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