🗄️ pg_dump diário em cron
O coração do Postiz é o Postgres: posts agendados, contas conectadas, tokens OAuth, métricas. Perder o volume é perder semanas de trabalho. A primeira camada de defesa é um pg_dump diário em formato -Fc (custom) — comprimido, mais rápido pra restaurar e permite restore seletivo de tabelas.
Use cron do host, não cron dentro do container. Container morre, cron morre junto. O dump roda via docker exec no Postgres já em execução.
# /usr/local/bin/postiz-backup.sh
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR=/var/backups/postiz
STAMP=$(date +%F_%H%M)
mkdir -p "$BACKUP_DIR"
# -Fc = custom format (comprimido + restore seletivo)
docker exec postiz-postgres \
pg_dump -U postiz -d postiz -Fc \
> "$BACKUP_DIR/postiz_${STAMP}.dump"
# Retenção local: 7 dias
find "$BACKUP_DIR" -name "postiz_*.dump" -mtime +7 -delete
echo "[$(date -Iseconds)] backup ok: postiz_${STAMP}.dump"
# Tornar executável e agendar (03:00 todo dia)
chmod +x /usr/local/bin/postiz-backup.sh
crontab -e
# adicionar:
0 3 * * * /usr/local/bin/postiz-backup.sh >> /var/log/postiz-backup.log 2>&1
💡 Dica prática
Sempre use set -euo pipefail no topo. Sem isso, um pg_dump que falha silenciosamente gera um arquivo de 0 bytes — e você só descobre no dia do desastre. Com pipefail, o script aborta e o cron envia e-mail.
Conceitos-chave
Custom format, comprimido, restaura seletivo.
Agendador do host — sobrevive a restart de container.
Janela de versões mantidas antes de purgar.
Falha do script propaga falha do pipeline.
☁️ Sync para S3 / R2 com restic
Dump no mesmo host onde o Postiz roda não é backup — é uma cópia. Se o disco morrer, morre junto. A camada off-site usa restic: encripta com AES-256 antes de subir, faz dedup em blocos (vários dumps quase iguais ocupam quase nada) e tem retenção declarativa.
Cloudflare R2 é a opção barata: zero egress fee, API S3-compatível, ~$0.015/GB-mês. AWS S3 funciona igual com endpoint default. O segredo de encriptação fica só no servidor — sem ele, o backup é lixo aleatório no bucket.
# Instalar restic
sudo apt install restic
# Variáveis (idealmente em /etc/postiz-backup.env, chmod 600)
export AWS_ACCESS_KEY_ID=<r2_access_key>
export AWS_SECRET_ACCESS_KEY=<r2_secret>
export RESTIC_REPOSITORY=s3:https://<account>.r2.cloudflarestorage.com/postiz-backups
export RESTIC_PASSWORD=<senha_forte_de_64_chars>
# Inicializar repo (UMA vez)
restic init
# Backup do diretório de dumps
restic backup /var/backups/postiz --tag daily
# Política de retenção: 7 diários, 4 semanais, 6 mensais
restic forget \
--keep-daily 7 --keep-weekly 4 --keep-monthly 6 \
--prune
# Adicionar ao postiz-backup.sh, após o pg_dump
source /etc/postiz-backup.env
restic backup "$BACKUP_DIR" --tag daily --quiet
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune --quiet
🔒 Dica prática
Guarde RESTIC_PASSWORD em dois lugares offline: gerenciador de senhas + papel no cofre. Se o servidor pegar fogo junto com a única cópia da senha, o backup encriptado no R2 vira inútil. Aconteceu. Mais de uma vez.
Conceitos-chave
Backup com dedup, encriptação e snapshots.
Blocos repetidos são armazenados uma só vez.
Aplica retenção e libera espaço do repositório.
Cópia em rede / região / provedor diferente.
✅ Teste de restore mensal
Backup não testado é boato. Você só descobre que o dump está corrompido, que faltou uma tabela, que o segredo está errado ou que ninguém anotou o procedimento — no dia em que precisa. Marque no calendário: primeiro domingo de cada mês, restore de teste num ambiente isolado.
O teste roda em uma VM ou container separado da produção. Restore o dump mais recente, suba um Postiz apontando para esse Postgres, faça login e verifique se posts agendados, contas conectadas e métricas voltaram.
# Em ambiente isolado (host de teste)
restic snapshots # listar backups disponíveis
restic restore latest --target /tmp/restore
# Subir Postgres temporário
docker run -d --name pg-restore \
-e POSTGRES_USER=postiz \
-e POSTGRES_PASSWORD=teste \
-e POSTGRES_DB=postiz \
postgres:16-alpine
# Restaurar o dump (-c = drop antes, --if-exists = idempotente)
cat /tmp/restore/var/backups/postiz/postiz_*.dump | \
docker exec -i pg-restore \
pg_restore -U postiz -d postiz -c --if-exists
# Smoke test: contar registros das tabelas críticas
docker exec pg-restore psql -U postiz -d postiz -c "
SELECT 'users' tbl, count(*) FROM users
UNION ALL SELECT 'posts', count(*) FROM posts
UNION ALL SELECT 'integrations', count(*) FROM integrations;"
# Limpar
docker rm -f pg-restore
✓ O que FAZER
- ✓Agendar o teste no calendário, com lembrete.
- ✓Documentar o procedimento num
RUNBOOK.md. - ✓Cronometrar — RTO real só sai do teste.
- ✓Conferir contagem de linhas em tabelas-chave.
✗ O que NÃO fazer
- ✗Restaurar em cima da produção — nunca.
- ✗"Confiar no log do cron" sem abrir o dump.
- ✗Pular o teste porque "está tudo funcionando".
- ✗Esquecer de remover o container de teste depois.
Conceitos-chave
Ensaio periódico do procedimento de recuperação.
Host/VM separados sem risco de afetar prod.
Verificações mínimas que dizem "voltou ok".
Cumprir o rito mesmo quando "está tudo bem".
🔄 Rotação de tokens
Tokens de longa duração são superfícies de ataque permanentes. Um vazamento de log, um backup que escapou, um dev que saiu da empresa — todos viram acesso eterno enquanto o token estiver válido. A política mínima é rotacionar tudo em ciclos previsíveis.
⏱️ Frequências recomendadas
- JWT_SECRET do Postiz: a cada 90 dias OU imediatamente após qualquer suspeita. Rotacionar invalida sessões — avise os usuários.
- Senha do Postgres: 180 dias. Lembre de atualizar
DATABASE_URLno.env. - API keys de redes sociais: seguir o ciclo do provedor — X expira em 2h (refresh automático), Meta a cada 60 dias, LinkedIn anual.
- Chaves S3/R2 do backup: 90 dias. Crie a nova, atualize o
.env, valide um backup, revogue a antiga. - SSH keys do servidor: anual + sempre após desligamento de pessoa.
# Rotacionar JWT_SECRET (zero-downtime parcial)
# 1) Gerar novo segredo
NEW=$(openssl rand -hex 64)
# 2) Atualizar .env
sed -i "s|^JWT_SECRET=.*|JWT_SECRET=${NEW}|" ~/postiz/.env
# 3) Reiniciar Postiz (sessões atuais caem — avise!)
docker compose -f ~/postiz/docker-compose.yml up -d --force-recreate postiz
# 4) Confirmar
docker compose logs --tail=20 postiz | grep -i ready
📅 Dica prática
Crie eventos recorrentes no calendário com o checklist da rotação. "Rotação de tokens — Q1/2026" no primeiro dia do trimestre. Sem rito agendado, ninguém rotaciona — e descobrir uma chave de 3 anos atrás num backup vazado é experiência humilhante.
Conceitos-chave
Substituir credencial ativa por uma nova.
Invalidar a credencial antiga no provedor.
Troca o access token sem novo login do user.
Escopo de impacto se a credencial vazar.
🔐 Princípio do menor privilégio
Toda credencial deve ter o mínimo de poder necessário pra fazer seu trabalho — e nada além disso. Token de backup acessa só o bucket de backup. Token do app acessa só o banco do app. Chave OAuth de Twitter só com escopos que o Postiz realmente usa.
Se um atacante conseguir uma credencial, o estrago é limitado pelo que ela pode fazer. Uma chave R2 com permissão de Object Read & Write em um único bucket é infinitamente melhor que uma chave master da conta.
# Exemplo: policy AWS S3 mínima para o backup
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::postiz-backups",
"arn:aws:s3:::postiz-backups/*"
]
}]
}
# SEM s3:* ou Resource: "*" — atacante não enumera outros buckets
✓ O que FAZER
- ✓Criar IAM user dedicado por função (backup, deploy, app).
- ✓Escopos OAuth mínimos — só os endpoints usados.
- ✓Rodar containers como user não-root (
user:no compose). - ✓Postgres do Postiz com role própria, sem SUPERUSER.
✗ O que NÃO fazer
- ✗Usar credencial root/master no servidor.
- ✗Reusar a mesma API key em deploy e em backup.
- ✗Pedir todos os escopos OAuth "por garantia".
- ✗Compartilhar a senha do Postgres com a app via env-debug.
Conceitos-chave
Principle of Least Privilege.
Regra explícita de quem faz o quê em que recurso.
Subconjunto de permissões pedidas ao usuário.
Uma identidade por função, nunca compartilhada.
🆘 Disaster recovery plan
Um DR plan responde duas perguntas antes do desastre: RTO (Recovery Time Objective — quanto tempo até voltar) e RPO (Recovery Point Objective — quanto de dados pode perder). Para um Postiz pessoal: RTO de 2h e RPO de 24h é razoável. Para SaaS pago: RTO <30min, RPO <1h exige replicação contínua.
A defesa robusta é a regra 3-2-1: três cópias, em dois meios diferentes, com uma off-site. Volume Docker (1), dump local em /var/backups (2), restic no R2 (3) — meios diferentes (disco do servidor vs. object storage), uma off-site (R2).
📋 Runbook mínimo — DR do Postiz
- 1. Provisionar host novo: VPS limpa, Docker instalado, DNS apontando.
- 2. Restaurar segredos:
.env,RESTIC_PASSWORD, chaves R2 (do gerenciador offline). - 3. Puxar último snapshot:
restic restore latest --target /. - 4. Subir Postgres vazio via
docker compose up -d postgres. - 5. Restaurar dump:
pg_restore -U postiz -d postiz -c --if-exists < backup.dump. - 6. Subir app:
docker compose up -de validar/api/health. - 7. Reconectar OAuth: alguns tokens podem precisar renovação manual no painel.
# Runbook em script — DR_RESTORE.sh (mantenha no repositório)
#!/usr/bin/env bash
set -euo pipefail
source /etc/postiz-backup.env
echo "[1/4] Baixando último snapshot..."
restic restore latest --target /tmp/dr
echo "[2/4] Subindo Postgres..."
cd ~/postiz && docker compose up -d postgres
until docker exec postiz-postgres pg_isready -U postiz; do sleep 2; done
echo "[3/4] Restaurando dump mais recente..."
LATEST=$(ls -t /tmp/dr/var/backups/postiz/postiz_*.dump | head -1)
docker exec -i postiz-postgres pg_restore -U postiz -d postiz -c --if-exists < "$LATEST"
echo "[4/4] Subindo aplicação..."
docker compose up -d
sleep 10
curl -fsS http://localhost:5000/api/health && echo "DR concluído ✓"
🎯 Dica prática
O runbook precisa estar acessível quando o servidor está fora do ar. Não adianta versionar só no repositório que vive no servidor que caiu. Mantenha cópia: GitHub privado + PDF impresso na gaveta + nota no gerenciador de senhas. Pareça paranoico — desastre acontece num domingo às 3h da manhã.
Conceitos-chave
Tempo máximo aceitável até a aplicação voltar.
Janela máxima de dados que pode-se perder.
3 cópias, 2 meios, 1 off-site. Mínimo viável.
Procedimento passo a passo, testado e acessível.
🎉 Parabéns! Você concluiu o curso MkBlogs
Da primeira ideia de "quero publicar online" até um Postiz em produção com domínio, HTTPS, monitoramento e plano de disaster recovery — você atravessou as seis trilhas. Não é pouca coisa. A maior parte dos blogs que existem hoje na internet foram colocados no ar por pessoas que sabiam menos do que você sabe agora.
O que você domina agora
🚀 Próximos passos
- • Publique algo público esta semana. Não precisa ser perfeito — precisa existir.
- • Agende a primeira rotação de tokens daqui a 90 dias no calendário.
- • Faça o primeiro restore drill ainda este mês, antes que você esqueça como funciona.
- • Volte aqui sempre que precisar revisar um conceito — o curso fica.
"A internet foi feita por pessoas que decidiram publicar. Agora você é uma delas."