DEV Community

Fernando Chagas
Fernando Chagas

Posted on Originally published at nuvits.net

Como instalar o n8n com Docker numa VPS

Este guia monta o n8n self-hosted para produção leve numa VPS Ubuntu 24.04: o n8n com PostgreSQL, o task runner num contêiner separado (como a documentação do n8n recomenda para produção) e o Caddy na frente, emitindo e renovando o certificado HTTPS. Tudo roda com Docker Compose a partir de três arquivos, e o guia também cobre atualização e backup.

O que você vai precisar

  • Uma VPS com Ubuntu 24.04 de 64 bits e acesso SSH com um usuário que tenha sudo.
  • Um domínio em que você consiga criar registros DNS. Os exemplos usam n8n.example.com.
  • Um e-mail para a conta ACME do certificado.

Quanto ao tamanho da máquina, a documentação do n8n não fixa um mínimo para este cenário. O guia oficial de Docker Compose pede 2 vCPU e 4 GB de RAM quando a instalação inclui o sandbox do n8n Assistant. Aqui são quatro contêineres (Caddy, n8n, runner e PostgreSQL), então trate 2 vCPU e 4 GB como piso. Com 4 vCPU e 8 GB sobra espaço para execuções com mais dados e para o banco crescer.

Se você só quer experimentar o n8n, a Nuvits também entrega a VPS com o n8n pré-instalado na contratação. Nessa opção, o n8n roda num único contêiner, sem PostgreSQL nem runner externo, e só responde dentro da VPS: o editor abre por um túnel SSH. Para receber webhooks da internet com domínio e HTTPS, siga este guia.

Preparar o servidor

Se o provedor entrega a VPS com acesso como root, crie um usuário com sudo e copie a sua chave SSH para ele:

adduser deploy
usermod -aG sudo deploy
install -d -m 700 -o deploy -g deploy /home/deploy/.ssh
install -m 600 -o deploy -g deploy ~/.ssh/authorized_keys /home/deploy/.ssh/
Enter fullscreen mode Exit fullscreen mode

Abra outra sessão como deploy e confira que o login funciona antes de fechar a de root. Depois, libere SSH, HTTP e HTTPS no ufw e ative o firewall:

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443
sudo ufw enable
sudo ufw status
Enter fullscreen mode Exit fullscreen mode

O ufw enable pede confirmação porque pode derrubar a conexão SSH; com a regra da porta 22 já criada, responda y. A regra 443 sem protocolo libera TCP e UDP. O UDP serve ao HTTP/3, que o Caddy também atende.

O Docker passa por cima do ufw. Uma porta publicada com ports: no Compose fica acessível de fora mesmo sem regra no ufw, porque o Docker desvia o tráfego antes que ele chegue às regras do firewall. Por isso, neste guia só o Caddy publica portas (80 e 443). O n8n e o PostgreSQL ficam apenas na rede interna do Compose.

Instalar o Docker

Na Nuvits, dá para pedir a VPS com o Docker já instalado na contratação. Nesse caso, pule para o docker compose version no fim desta seção.

O caminho que a documentação do Docker indica para servidores é o repositório apt oficial. Primeiro, remova pacotes não oficiais que possam conflitar (numa instalação limpa, o apt costuma responder que não há nada para remover):

sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runc | cut -f1)
Enter fullscreen mode Exit fullscreen mode

Adicione o repositório e a chave GPG do Docker:

sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

sudo apt update
Enter fullscreen mode Exit fullscreen mode

Instale o Docker Engine e o plugin do Compose, depois teste:

sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo docker run hello-world
docker compose version
Enter fullscreen mode Exit fullscreen mode

Existe também o script get.docker.com, mas a própria documentação do Docker o indica só para ambientes de teste e desenvolvimento, não para produção.

Os comandos deste guia usam sudo docker. Você pode adicionar seu usuário ao grupo docker para dispensar o sudo, mas a documentação do Docker avisa que esse grupo dá privilégios equivalentes aos de root.

Configurar o n8n

Crie uma pasta para o projeto. O Compose usa o nome dela como prefixo de volumes e contêineres:

mkdir -p ~/n8n && cd ~/n8n
Enter fullscreen mode Exit fullscreen mode

Segredos no .env

Gere três valores aleatórios, um para a chave de criptografia, um para o token do runner e um para a senha do banco:

openssl rand -hex 32
Enter fullscreen mode Exit fullscreen mode

Rode o comando três vezes e cole os resultados no .env:

N8N_VERSION=2.47.4
N8N_DOMAIN=n8n.example.com
ACME_EMAIL=voce@example.com
GENERIC_TIMEZONE=America/Sao_Paulo

N8N_ENCRYPTION_KEY=cole-aqui-o-primeiro-valor
N8N_RUNNERS_AUTH_TOKEN=cole-aqui-o-segundo-valor

POSTGRES_USER=n8n
POSTGRES_PASSWORD=cole-aqui-o-terceiro-valor
POSTGRES_DB=n8n
Enter fullscreen mode Exit fullscreen mode
chmod 600 .env
Enter fullscreen mode Exit fullscreen mode

N8N_VERSION fixa a versão das imagens do n8n e do runner, que precisam ser iguais. Use a versão stable mais recente listada nas releases do n8n; 2.47.4 era a estável quando este texto foi escrito.

Guarde a chave. O n8n usa N8N_ENCRYPTION_KEY para criptografar as credenciais salvas no banco. Sem ela, um backup restaurado abre, mas nenhuma credencial pode ser lida. Guarde uma cópia do .env fora da VPS, num gerenciador de senhas, por exemplo.

Caddyfile

O Caddy obtém o certificado sozinho quando encontra um nome de domínio no endereço do site. O flush_interval -1 desliga o buffer da resposta, como no exemplo oficial do n8n com Caddy:

{
    email {$ACME_EMAIL}
}

{$N8N_DOMAIN} {
    reverse_proxy n8n:5678 {
        flush_interval -1
    }
}
Enter fullscreen mode Exit fullscreen mode

O Caddy substitui {$N8N_DOMAIN} e {$ACME_EMAIL} pelas variáveis de ambiente do contêiner, que o Compose preenche a partir do .env. O reverse_proxy já envia os cabeçalhos X-Forwarded-For, X-Forwarded-Proto e X-Forwarded-Host, que o n8n precisa receber atrás de um proxy.

docker-compose.yml

services:
  caddy:
    image: caddy:2.11
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    environment:
      - N8N_DOMAIN=${N8N_DOMAIN}
      - ACME_EMAIL=${ACME_EMAIL}
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - n8n

  postgres:
    image: postgres:18
    restart: always
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
      - PGDATA=/var/lib/postgresql/data
    volumes:
      - db_storage:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 10

  n8n:
    image: docker.n8n.io/n8nio/n8n:${N8N_VERSION}
    restart: always
    environment:
      - N8N_HOST=${N8N_DOMAIN}
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - N8N_WEBHOOK_URL=https://${N8N_DOMAIN}/
      - N8N_PROXY_HOPS=1
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
      - GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
      - TZ=${GENERIC_TIMEZONE}
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
      - DB_POSTGRESDB_USER=${POSTGRES_USER}
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      - N8N_RUNNERS_MODE=external
      - N8N_RUNNERS_AUTH_TOKEN=${N8N_RUNNERS_AUTH_TOKEN}
      - N8N_RUNNERS_BROKER_LISTEN_ADDRESS=0.0.0.0
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy

  n8n-runner:
    image: n8nio/runners:${N8N_VERSION}
    restart: always
    environment:
      - N8N_RUNNERS_AUTH_TOKEN=${N8N_RUNNERS_AUTH_TOKEN}
      - N8N_RUNNERS_TASK_BROKER_URI=http://n8n:5679
    depends_on:
      - n8n

volumes:
  caddy_data:
  caddy_config:
  db_storage:
  n8n_data:
Enter fullscreen mode Exit fullscreen mode

O que vale saber sobre esse arquivo:

  • N8N_WEBHOOK_URL e N8N_PROXY_HOPS=1 fazem o n8n montar as URLs de webhook com o domínio público e confiar nos cabeçalhos do Caddy. Sem isso, o editor mostra URLs com a porta interna 5678. Em versões anteriores à 2.30.0, a variável se chama WEBHOOK_URL, hoje obsoleta.
  • O runner externo executa o código dos nós Code fora do processo do n8n. O modo interno, que é o padrão, está obsoleto e a documentação o desaconselha em produção: código que escape do sandbox fica com o mesmo acesso do n8n, inclusive às credenciais.
  • O PGDATA fixo mantém os dados do Postgres 18 dentro do volume. Não remova essa linha: sem ela, o Postgres 18 grava fora do volume e o banco começa vazio.
  • O n8n continua com o volume em /home/node/.n8n mesmo usando PostgreSQL. A documentação recomenda manter essa pasta persistente.
  • Nenhuma porta do n8n ou do Postgres é publicada. O Caddy alcança o n8n pelo nome do serviço na rede do Compose.

Apontar o DNS e subir

No painel DNS do seu domínio, crie um registro A com o nome n8n (ou o subdomínio escolhido) apontando para o IPv4 da VPS. Antes de subir, confira se o nome já resolve para o IP certo:

dig +short n8n.example.com A
Enter fullscreen mode Exit fullscreen mode

Com o DNS resolvendo, suba a stack:

cd ~/n8n
sudo docker compose up -d
sudo docker compose ps
Enter fullscreen mode Exit fullscreen mode

O n8n só sobe depois que o Postgres passa no healthcheck. Acompanhe os logs do n8n e do Caddy até ver o editor disponível e o certificado emitido:

sudo docker compose logs -f n8n
sudo docker compose logs caddy
Enter fullscreen mode Exit fullscreen mode

Se o certificado não sair, confira se o DNS já propagou e se as portas 80 e 443 também estão abertas no firewall do provedor, quando houver um. Depois de corrigir, rode sudo docker compose restart caddy.

Por fim, teste de fora da VPS:

curl -fsS https://n8n.example.com/healthz
Enter fullscreen mode Exit fullscreen mode

Abra https://n8n.example.com no navegador e crie a conta de owner. Para conferir os webhooks, adicione um nó Webhook a um workflow: a URL de produção deve começar com https://n8n.example.com/webhook/.

Atualizar e fazer backup

Backup

Um backup completo do n8n com PostgreSQL tem duas partes: o banco e a pasta .n8n, que guarda a configuração e a chave de criptografia. Com a stack no ar:

cd ~/n8n
set -a && . ./.env && set +a

sudo docker compose exec -T postgres pg_dumpall -U "$POSTGRES_USER" > n8n-db-$(date +%F).sql
grep -c "PostgreSQL database cluster dump complete" n8n-db-$(date +%F).sql

sudo docker run --rm --volumes-from "$(sudo docker compose ps -q n8n)" -v "$PWD":/backup ubuntu:24.04 tar czf /backup/n8n-data-$(date +%F).tar.gz /home/node/.n8n
Enter fullscreen mode Exit fullscreen mode

O grep precisa imprimir 1. Se imprimir 0, o dump ficou incompleto. Depois, copie os dois arquivos e o .env para fora da VPS, a partir da sua máquina:

mkdir -p backups-n8n
scp 'deploy@IP_DA_VPS:n8n/n8n-*' 'deploy@IP_DA_VPS:n8n/.env' ./backups-n8n/
Enter fullscreen mode Exit fullscreen mode

Um backup guardado só no disco da própria VPS se perde junto com ela. Para restaurar, a ordem é parar o n8n, devolver a pasta .n8n e o banco e subir de novo com o mesmo .env. A página Back up and restore detalha cada passo.

Nunca rode docker compose down -v nesta pasta. O -v apaga também os volumes, incluindo o n8n_data com a chave de criptografia e o db_storage com o banco.

Atualizar

O n8n lança uma versão nova quase toda semana. A documentação sugere atualizar pelo menos uma vez por mês, para não pular muitas versões de uma vez, e ler as notas de versão antes. O roteiro:

  1. Faça o backup acima.
  2. Troque N8N_VERSION no .env pela nova versão stable. O runner acompanha, porque usa a mesma variável.
  3. Baixe as imagens e recrie os contêineres:
cd ~/n8n
sudo docker compose pull
sudo docker compose down
sudo docker compose up -d
sudo docker compose logs -f n8n
Enter fullscreen mode Exit fullscreen mode

O n8n aplica as migrações do banco ao iniciar. Trocar a tag de volta não desfaz essas migrações, e é por isso que o backup vem antes. O docker compose pull também traz a última versão 2.11 do Caddy e a última versão 18 do PostgreSQL. Mudar o Postgres de versão principal (de 18 para 19, por exemplo) exige dump e restauração, não só trocar a tag.

Próximos passos

  • Ative a autenticação em dois fatores na conta de owner e configure SMTP para convites e redefinição de senha, com as variáveis de user management.
  • Agende o backup com cron e mande as cópias para outro lugar automaticamente.
  • Separe um usuário do Postgres sem privilégios de superusuário para o n8n, como no exemplo withPostgres do repositório oficial.
  • Leia o guia de hardening dos task runners se mais pessoas forem editar workflows na instância.

Top comments (0)