Ir para o conteúdo

Tutorial

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.

Neste artigo
  1. O que você vai precisar
  2. Preparar o servidor
  3. Instalar o Docker
  4. Configurar o n8n
  5. Apontar o DNS e subir
  6. Atualizar e fazer backup
  7. Próximos passos

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:

bash
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/

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:

bash
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443
sudo ufw enable
sudo ufw status

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.

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):

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

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

bash
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

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

bash
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo docker run hello-world
docker compose version

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:

bash
mkdir -p ~/n8n && cd ~/n8n

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:

bash
openssl rand -hex 32

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

.env
N8N_VERSION=2.47.4
N8N_DOMAIN=n8n.example.com
[email protected]
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
bash
chmod 600 .env

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.

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:

Caddyfile
{
	email {$ACME_EMAIL}
}

{$N8N_DOMAIN} {
	reverse_proxy n8n:5678 {
		flush_interval -1
	}
}

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

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:

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:

bash
dig +short n8n.example.com A

Com o DNS resolvendo, suba a stack:

bash
cd ~/n8n
sudo docker compose up -d
sudo docker compose ps

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:

bash
sudo docker compose logs -f n8n
sudo docker compose logs caddy

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:

bash
curl -fsS https://n8n.example.com/healthz

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:

bash
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

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:

bash
mkdir -p backups-n8n
scp 'deploy@IP_DA_VPS:n8n/n8n-*' 'deploy@IP_DA_VPS:n8n/.env' ./backups-n8n/

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.

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:
bash
cd ~/n8n
sudo docker compose pull
sudo docker compose down
sudo docker compose up -d
sudo docker compose logs -f n8n

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.
Fernando Chagas

Constrói a Nuvits, VPS paga em reais com MCP para agentes de IA.

Read in English