infra/backup.sh salva tudo que importa numa pasta versionada por data, com retenção dos últimos 4 backups. Roda via cron uma vez por semana.

O que entra no backup

ArquivoO que tem
postgres-all.sql.gzpg_dumpall — todas as DBs + roles + senhas hashadas
n8n-data.tar.gzinfra/n8n/data/ — workflows, credenciais cifradas, encryption key
npm-data.tar.gzinfra/npm/data/ + infra/npm/letsencrypt/ — proxy hosts, certificados TLS
envs.tar.gz.env raiz, .env de cada serviço, postgres/secrets/
Não entra:
  • Redis (cache/queue, descartável — n8n persiste em Postgres)
  • Código de projetos em /opt/server/projetos/ — cada um tem repo git próprio

Setup inicial

1

Criar diretório de destino

Default é /opt/backups. Precisa de sudo só uma vez:
sudo mkdir -p /opt/backups
sudo chown $USER:$USER /opt/backups
Pra usar outro destino, exporte BACKUP_DIR antes de rodar o script (ou no cron).
2

Testar manualmente

/opt/server/infra/backup.sh
Deve criar /opt/backups/<timestamp>/ com 4 arquivos .gz e um symlink latest →.
3

Agendar no cron

Editar crontab do user (não precisa root):
crontab -e
Adicionar:
0 3 * * 0 /opt/server/infra/backup.sh >> /opt/backups/backup.log 2>&1
Roda todo domingo 03:00. A saída vai pra /opt/backups/backup.log (cresce devagar — uma execução = ~10 linhas).Confirmar com crontab -l.

Configuração

Variáveis de ambiente (todas opcionais):
VarDefaultO que faz
BACKUP_DIR/opt/backupsOnde salvar
RETENTION4Quantos backups manter (resto é deletado)
Exemplo customizado no cron:
0 3 * * 0 BACKUP_DIR=/mnt/external RETENTION=12 /opt/server/infra/backup.sh >> /opt/backups/backup.log 2>&1

Restaurar

Postgres (uma DB específica)

O dump é pg_dumpall, que recria roles + DBs do zero. Pra restaurar uma DB específica sem mexer no resto:
# extrai só a seção da DB alvo
zcat /opt/backups/<timestamp>/postgres-all.sql.gz \
  | awk '/^\\connect sistemashalom$/,/^\\connect /' > /tmp/shalom.sql

# recria DB do zero (cuidado: apaga o que tiver)
docker exec -i postgres psql -U postgres -c "DROP DATABASE IF EXISTS sistemashalom;"
docker exec -i postgres psql -U postgres -c "CREATE DATABASE sistemashalom OWNER sistemashalom;"
docker exec -i postgres psql -U sistemashalom -d sistemashalom < /tmp/shalom.sql

Postgres (cluster inteiro, disaster recovery)

Em servidor novo, depois do bootstrap.sh + postgres up -d:
zcat postgres-all.sql.gz | docker exec -i postgres psql -U postgres
Isso recria roles, DBs e dados — o estado fica idêntico ao do backup.

n8n

Parar o n8n, restaurar o data/ por cima, subir de novo:
cd /opt/server/infra/n8n
docker compose down
sudo rm -rf data
sudo tar -xzf /opt/backups/<timestamp>/n8n-data.tar.gz
sudo chown -R 1000:1000 data
docker compose up -d
A N8N_ENCRYPTION_KEY precisa ser a mesma do backup (vem em envs.tar.gz), senão as credenciais cifradas ficam ilegíveis.

NPM

Mesmo padrão:
cd /opt/server/infra/npm
docker compose down
sudo rm -rf data letsencrypt
sudo tar -xzf /opt/backups/<timestamp>/npm-data.tar.gz
docker compose up -d
Os proxy hosts e certificados voltam exatamente como estavam.

Envs

cd /opt/server/infra
tar -xzf /opt/backups/<timestamp>/envs.tar.gz
Sobrescreve os .env atuais — útil quando recria servidor do zero, antes do primeiro docker compose up.

Verificar saúde

Olhar a última execução:
tail -20 /opt/backups/backup.log
ls -lh /opt/backups/latest/
Se um arquivo está faltando ou com tamanho zero, o cron silenciou erro — rodar manual pra ver o stderr.

Limitações conhecidas

  • Backup local apenas. Se o disco do VPS morrer, perdeu junto. Pra cobrir isso, sincronizar /opt/backups/ pra outro lugar (rsync remoto, S3, snapshot do provedor).
  • NPM SQLite copiado online. O backup roda com NPM ligado. SQLite em modo WAL geralmente sobrevive, mas em teoria pode pegar estado parcial. Se preocupar, parar o NPM antes (docker compose stop npm → backup → start).
  • Sem alerta de falha. Cron silencia erros por padrão. Pra ser avisado, redirecionar stderr pra um canal (email, ntfy, etc.).