Обновление инфраструктуры — это рутина. Но когда нужно перевезти проект со старого стека, пропустив несколько мажорных версий софта, начинается самое интересное. Недавно я проводил жесткую миграцию 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. Открыт к предметному диалогу и интересным проектам.
Автор: chernyaevi
