# Como instalar o n8n com Docker numa VPS | Nuvits

Fonte: [https://nuvits.net/blog/como-instalar-n8n-docker-vps](https://nuvits.net/blog/como-instalar-n8n-docker-vps)

Publicado em 2026-11-11, por Fernando Chagas.

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:

```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.

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

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

```ini .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
```

```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](https://github.com/n8n-io/n8n/releases); `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:

```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

```yaml 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](https://docs.n8n.io/deploy/host-n8n/keep-n8n-running/backup-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](https://docs.n8n.io/changelog/release-notes-2.x) 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](https://docs.n8n.io/deploy/host-n8n/configure-n8n/basic-configuration/use-environment-variables/user-management-and-2fa).
- 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](https://github.com/n8n-io/n8n-hosting/tree/main/docker-compose/withPostgres) do repositório oficial.
- Leia o guia de [hardening dos task runners](https://docs.n8n.io/deploy/host-n8n/configure-n8n/security/harden-task-runners) se mais pessoas forem editar workflows na instância.
