← Todos os artigos
5 min de leitura

Meus 11 workflows do n8n sumiram — a culpa era de um || true

O n8n abriu na tela de primeira automação com o banco intacto no disco. A causa era uma cadeia de três erros silenciosos em Docker. Como diagnosticar e evitar.

n8nDockerDevOps

Abri o n8n e dei de cara com a tela de boas-vindas: "Let's build your first automation". Onze workflows, seis deles ativos, rodando havia meses. Sumidos.

O detalhe que me impediu de entrar em pânico: o database.sqlite estava lá no volume, com 8 MB e data de modificação de duas semanas antes. Os dados existiam. O n8n é que não estava olhando para eles.

Foram três erros encadeados, e o que torna esse caso interessante é que nenhum deles gerou um alerta. Cada camada escondia a seguinte.

Sintoma inicial: 502 no domínio

O primeiro sinal foi um 502 em n8n.joaovitral.com. O Cloudflare respondia, o túnel estava ativo, mas nada atendia do outro lado:

curl -s -o /dev/null -w "%{http_code}" https://n8n.exemplo.com
# 502

ss -tln | grep 5678
# (vazio — ninguém escutando)

O container não estava apenas parado. Ele não existia:

docker ps -a --filter "name=n8n"
# so aparecem containers auxiliares, nenhum do n8n em si

Um docker compose down tinha rodado dias antes. O up que deveria vir depois falhou — e ninguém percebeu.

Erro 1: useradd colidindo com o UID da imagem base

O Dockerfile tentava criar um usuário node com UID 1000:

FROM mcr.microsoft.com/playwright:v1.58.0-noble

RUN useradd -m -u 1000 -s /bin/bash node 2>/dev/null || true \
    && mkdir -p /home/node/.n8n \
    && chown -R node:node /home/node

O problema: a imagem base do Playwright já traz um usuário no UID 1000.

docker run --rm --entrypoint sh mcr.microsoft.com/playwright:v1.58.0-noble \
  -c "getent passwd 1000"
# ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash

O useradd falhava. Mas repare no || true: ele engolia o erro e deixava o build seguir. O comando seguinte, chown node:node, quebrava de verdade — porque o usuário node nunca chegou a existir:

chown: invalid user: 'node:node'

O || true foi escrito para tornar o build idempotente ("se o usuário já existe, tudo bem"). Só que ele não distingue "já existe" de "não pôde ser criado". As duas situações somem igual, e a falha real só aparece três linhas depois, num comando aparentemente sem relação.

A correção foi parar de criar usuário e usar o UID numérico — que é, afinal, o que importa para permissão de arquivo:

# A imagem base ja traz "ubuntu" no UID 1000 — o mesmo dono dos arquivos do
# volume. Ajustar por UID evita a colisao com o useradd.
RUN mkdir -p /home/node/.n8n \
    && chown -R 1000:1000 /home/node

USER 1000

Erro 2: N8N_USER_FOLDER não é a pasta que você pensa

Build corrigido, container de pé, HTTP 200. E a tela de primeira automação.

O motivo estava numa variável que parecia certa:

ENV N8N_USER_FOLDER=/home/node/.n8n

O n8n acrescenta .n8n ao valor dessa variável. Apontando para /home/node/.n8n, ele passou a usar /home/node/.n8n/.n8n/ — uma pasta nova dentro do volume, com um banco zerado:

ls /home/node/.n8n/
# database.sqlite   <- o banco real, 8 MB, intacto
# .n8n/             <- pasta criada hoje, com um sqlite vazio

Antes isso não acontecia por acaso: o usuário node tinha HOME=/home/node, e o n8n usava $HOME/.n8n naturalmente. Ao trocar para o UID 1000 (cujo home é /home/ubuntu), o HOME mudou e a variável passou a valer.

O valor correto é o diretório pai:

ENV N8N_USER_FOLDER=/home/node

Erro 3: o HOME que levou junto o cache do Playwright

Faltava uma consequência. Um community node procurava os navegadores em $HOME/.cache/ms-playwright e, com o HOME alterado, não encontrava. Tentava rebaixar tudo, falhava e derrubava o container em loop:

Error during browser setup: Failed to install browsers.
Expected path /home/ubuntu/.cache/ms-playwright does not exist

A solução foi restaurar o HOME e apontar o cache para os navegadores que a imagem já traz:

RUN mkdir -p /home/node/.n8n /home/node/.cache \
    && ln -sfn /ms-playwright /home/node/.cache/ms-playwright \
    && chown -R 1000:1000 /home/node

ENV HOME=/home/node
ENV N8N_USER_FOLDER=/home/node
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright

Como verificar que voltou

Três checagens, nesta ordem — cada uma isola uma das camadas:

# 1. O container subiu e nao esta reiniciando em loop
docker inspect n8n-n8n-1 --format "{{.State.Status}} restarts={{.RestartCount}}"
# running restarts=0

# 2. O n8n abriu o banco certo (nao pode existir .n8n dentro de .n8n)
docker exec n8n-n8n-1 ls -la /home/node/.n8n/database.sqlite
docker exec n8n-n8n-1 ls -d /home/node/.n8n/.n8n 2>/dev/null \
  && echo "ALERTA: pasta duplicada" || echo "OK"

# 3. Os dados estao la
sqlite3 /var/lib/docker/volumes/n8n_n8n_data/_data/database.sqlite \
  "SELECT COUNT(*) FROM workflow_entity;"
# 11

O que eu levo daqui

|| true é uma mentira contada ao seu build. Ele não diz "isso é opcional" — diz "não quero saber se falhou". Quando precisar de idempotência, teste a condição de verdade:

RUN id -u node >/dev/null 2>&1 || useradd -m -u 1000 node

Variável de ambiente com caminho merece uma conferida na documentação. N8N_USER_FOLDER parece pedir a pasta final e pede a pasta pai. Um ls dentro do container depois do primeiro start custa dez segundos e teria matado esse bug na hora.

Trocar o usuário de um container mexe em mais coisa do que parece. HOME muda, e junto vão os caches, os arquivos de configuração e qualquer coisa que dependa de ~.

Antes de mexer em volume, faça um snapshot. Foi a decisão que transformou um susto em inconveniente:

cp /var/lib/docker/volumes/n8n_n8n_data/_data/database.sqlite \
   /root/n8n-snapshot-$(date +%Y%m%d-%H%M).sqlite

E a lição que amarra todas: um serviço que cai em silêncio fica caído. Não havia alerta nenhum apontando para o n8n fora do ar — o que fez um docker compose up esquecido virar duas semanas de automações paradas. Monitoramento de uptime é mais barato que arqueologia de logs.

Gostou do artigo?

Estou sempre aberto a conversar sobre projetos, oportunidades e ideias.

Falar comigo