Обновление инфраструктуры — это рутина. Но когда нужно перевезти проект со старого стека, пропустив несколько мажорных версий софта, начинается самое интересное. Недавно я проводил жесткую миграцию n8n с легаси-связки сразу на актуальные версии: Traefik 3.3 и Postgres 16.

В этой статье поделюсь инструкцией по переносу, в которой учтены неочевидные баги несовместимости Traefik 3 с Docker API, политика безопасности свежего Postgres и конфликты ключей шифрования.

Шаг 1. Бэкап базы и спасение ключей

Первое правило миграции n8n — не забыть про ключ шифрования. Без него на новом сервере база поднимется, но все сохраненные учетные данные (Credentials) превратятся в тыкву.

  • Снимаем дамп базы в формате custom для утилиты pg_restore:

docker exec -t postgres_db pg_dump -U root -d n8n -Fc > /tmp/n8n_backup.dump
docker cp postgres_db:/tmp/n8n_backup.dump ./n8n_backup.dump
  • Вытаскиваем старый ключ шифрования. Это действие ОБЯЗАТЕЛЬНО, иначе отвалятся все доступы. Найти его можно в старом конфиге:

cat ~/.n8n/config | grep encryptionKey
  • Альтернативный вариант — найти переменную N8N_ENCRYPTION_KEY в старом файле docker-compose.yml.

  • После этого переносим полученный файл n8n_backup.dump на новый сервер.

Шаг 2. Развертывание и грабли Traefik 3.3 (Конфликт Docker API)

Создаем рабочую директорию и пишем docker-compose.yml.

Здесь кроется главная засада, с которой пришлось жестко повозиться. При попытке поднять стек Traefik v3.3 упорно отказывался видеть контейнеры и сыпал ошибками 404.

Оказалось, что роутер по дефолту обращался к Docker API как очень старый клиент (версии 1.24), в то время как современный сервер (демон Docker) требовал клиента версии не ниже 1.41. Возникал конфликт версий, из-за которого Traefik просто не мог получить список запущенных сервисов.

Лечится это добавлением одной строчки прямо в секцию environment для контейнера Traefik:

    environment:
      - DOCKER_API_VERSION=1.41

Это заставило роутер забыть про дефолтный старый API и начать общаться с демоном Docker на актуальном языке. Как только Traefik переключился на версию 1.41, он тут же увидел запущенный контейнер n8n, прочитал его метки (labels), и ошибка 404 исчезла.

Ну и базовая сетевая гигиена: для надежности мы сажаем все контейнеры (Traefik, n8n и Postgres) в одну общую изолированную сеть n8n_net, чтобы они гарантированно видели друг друга без сбоев маршрутизации.

Итоговый конфиг выглядит следующим образом:

version: '3.8'

volumes:
  db_storage:
  n8n_storage:
  traefik_certs:

networks:
  n8n_net:
    name: n8n_net

services:
  postgres_db:
    image: postgres:16
    container_name: postgres_db
    restart: always
    environment:
      - POSTGRES_USER=root
      - POSTGRES_PASSWORD=твой_пароль_от_базы
      - POSTGRES_DB=n8n
    volumes:
      - db_storage:/var/lib/postgresql/data
    networks:
      - n8n_net

  traefik:
    image: traefik:v3.3
    container_name: traefik
    restart: unless-stopped
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
      - "--certificatesresolvers.myresolver.acme.email=твоя_почта@mail.com"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
    environment:
      # Заставляем Traefik говорить с демоном на актуальном языке API
      - DOCKER_API_VERSION=1.41
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - traefik_certs:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - n8n_net

  n8n:
    image: docker.n8n.io/n8nio/n8n:latest
    container_name: n8n
    restart: always
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_HOST=postgres_db
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_USER=root
      - DB_POSTGRESDB_PASSWORD=твой_пароль_от_базы
      # Строго старый ключ шифрования, иначе доступы выдадут "Needs first setup"
      - N8N_ENCRYPTION_KEY=твой_старый_ключ
      - N8N_HOST=твой_домен.com
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - NODE_ENV=production
      - WEBHOOK_URL=https://твой_[домен.com/](https://домен.com/)
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.n8n.rule=Host(`твой_домен.com`)"
      - "traefik.http.routers.n8n.entrypoints=websecure"
      - "traefik.http.routers.n8n.tls.certresolver=myresolver"
    volumes:
      - n8n_storage:/home/node/.n8n
    networks:
      - n8n_net
    depends_on:
      - postgres_db

Запускаем стек командой docker compose up -d.

Шаг 3. Восстановление базы и фикс прав (Postgres 16)

  • Закидываем дамп в контейнер базы и разворачиваем его:

docker cp n8n_backup.dump postgres_db:/tmp/
docker exec -i postgres_db pg_restore -U root -d n8n -1 /tmp/n8n_backup.dump
  • Здесь возникает следующий критичный момент: Postgres 16 по умолчанию сильно урезает права на схему public.

  • Из-за этого ограничения n8n после запуска уйдет в цикличный ребут с ошибкой permission denied for schema public.

  • Проблема лечится выдачей полных прав владельцу базы:

docker exec -it postgres_db psql -U root -d n8n -c "GRANT ALL ON SCHEMA public TO root;"

Шаг 4. Фикс Credentials (ошибка "Needs first setup")

Даже если вы правильно указали переменную N8N_ENCRYPTION_KEY в файле compose, при первом старте n8n генерирует новый локальный файл config внутри примонтированного volume.

В результате возникает конфликт: база поднимается, но сохраненные Credentials отказываются расшифровываться. Чтобы это исправить, необходимо удалить этот свежесозданный локальный конфиг. Тогда n8n принудительно возьмет ключ из переменных среды (environment). Удаляем и рестартанем контейнер:

docker exec -it n8n rm /home/node/.n8n/config
docker compose restart n8n

Шаг 5. Бонус: Фикс фоновых парсеров (SQLite Lock)

Этот пункт актуален для тех, у кого на сервере крутятся фоновые Python-скрипты (например, парсеры на Telethon) в виде systemd-служб.

После жесткого переезда они могут уйти в бесконечный рестарт из-за блокировки файла сессии. В логах будет висеть ошибка sqlite3.OperationalError: database is locked.

Решается это принудительным завершением зомби-процессов и удалением временных журналов SQLite. Сама .session при этом не пострадает, поэтому заново авторизовываться по номеру телефона в Telegram не придется:

apt install psmisc -y
fuser -k -v /root/parser_folder/session_name.session
rm -f /root/parser_folder/session_name.session-journal
rm -f /root/parser_folder/session_name.session-wal
systemctl restart имя_службы.service

Итоги

На этом миграция завершена. Переход на мажорные версии БД и реверс-прокси всегда сопровождается подводными камнями, но если следовать этому алгоритму, все интеграции внутри n8n поднимаются без необходимости перевыпускать токены и перенастраивать вебхуки.

P.S. Я профессионально занимаюсь автоматизацией бизнеса и проектированием сложных архитектур на n8n. Если ваш проект уперся в технический потолок или вы ищете специалиста для решения нетривиальных задач — стучитесь ко мне в Telegram. Открыт к предметному диалогу и интересным проектам.

Комментарии (2)


  1. viktdo
    19.04.2026 07:38

    Спасибо, пункт про DOCKER_API_VERSION=1.41 сэкономил бы нам пару часов — ловили ровно этот же симптом на Traefik 3.3 и сначала грешили на лейблы. Ещё добавлю из своего опыта миграций n8n: перед pg_restore стоит временно выключить воркеры (N8N_DISABLE_PRODUCTION_MAIN_PROCESS + остановить queue-режим), иначе на больших инстансах ловили гонку — executions начинали писаться в ещё не до конца восстановленную схему. И да, N8N_ENCRYPTION_KEY — это то, на чём обжигаются буквально все, кто мигрирует впервые; хорошо, что вы вынесли это отдельным акцентом.


    1. chernyaevi Автор
      19.04.2026 07:38

      Привет! Спасибо за фидбек. Про отключение воркеров перед pg_restore — прям в точку, шикарное дополнение для крупных инстансов, кому-то точно спасет базу от каши. Рад, что нюанс с DOCKER_API_VERSION сэкономил вам время!