Backup de Bases de Dados SQLite em Produção: Estratégias para Aplicações Self-Hosted
Linux · SQLite · Backup · Self-Hosted · Databases | ✎ Duarte Spínola | 11 de Julho de 2026
O SQLite é o motor de base de dados mais usado no mundo — está em praticamente todas as aplicações self-hosted que um sysadmin PME encontra: Nextcloud, Authelia, Grafana, Home Assistant, Prometheus, e centenas de serviços Docker. Ao contrário do PostgreSQL ou MySQL, o SQLite é um ficheiro único no disco, o que torna o backup aparentemente trivial — copiar o ficheiro. Mas copiar um ficheiro SQLite enquanto está em uso pode produzir um backup corrompido. Este guia cobre estratégias de backup consistentes (hot e cold), automação com cron, validação de integridade, retenção 3-2-1, e recuperação passo-a-passo.
Conteúdos
- 1. O que Está a Acontecer — SQLite em Produção
- 2. Cenários em que o Backup SQLite é Crítico
- 3. Método 1 — Hot Backup com .backup (Recomendado)
- 4. Método 2 — Cold Backup (Cópia de Ficheiro)
- 5. Método 3 — VACUUM INTO (Backup Otimizado)
- 6. Método 4 — Backup via Docker Exec
- 7. Automação com Cron — Script de Backup
- 8. Validação de Integridade — PRAGMA integrity_check
- 9. Retenção 3-2-1 e Rotação de Backups
- 10. Recuperação — Restaurar de Backup
- 11. FAQ para Sysadmins
- 12. Erros Comuns e Resoluções
1. O que Está a Acontecer — SQLite em Produção
O SQLite é um base de dados embutida (embedded) — não tem servidor separado, corre no mesmo processo da aplicação. A base de dados inteira é um ficheiro (.db, .sqlite, .sqlite3) no disco. Isto tem vantagens (simplicidade, sem configuração, sem portas de rede) mas traz um desafio crítico para backups: copiar o ficheiro enquanto a aplicação está a escrever produz um backup inconsistente ou corrompido.
O SQLite usa WAL (Write-Ahead Logging) por defeito em muitas aplicações. Com WAL activo, há três ficheiros: database.db, database.db-wal (transacções pendentes), e database.db-shm (memória partilhada). Copiar apenas o .db sem o -wal perde transacções não confirmadas.
⚠ NUNCA faça cp database.db backup.db enquanto a aplicação está a correr. O ficheiro pode estar a meio de uma escrita. O backup resultante pode estar corrompido ou ter transacções perdidas. Use o comando .backup do SQLite (Método 1) que garante consistência.
2. Cenários em que o Backup SQLite é Crítico
| Aplicação | Localização típica do DB | Método recomendado |
|---|---|---|
| Nextcloud | /var/www/nextcloud/data/owncloud.db | Método 1 (.backup) ou occ command |
| Authelia | /config/authelia.sqlite | Método 4 (Docker exec) |
| Grafana | /var/lib/grafana/grafana.db | Método 1 (.backup) |
| Home Assistant | /config/home-assistant_v2.db | Método 1 (.backup) ou HA backup |
| Prometheus | N/A (usa TSDB própria) | Usar snapshots do Prometheus |
| Vaultwarden (Bitwarden) | /data/db.sqlite3 | Método 4 (Docker exec) |
| App custom (Python, Go) | Definido pelo developer | Método 1 (.backup) ou Método 3 (VACUUM INTO) |
3. Método 1 — Hot Backup com .backup (Recomendado)
O comando .backup do SQLite CLI faz um backup consistente mesmo com a base de dados em uso. Usa a API sqlite3_backup_init() que lê transação a transação de forma segura:
# Backup consistente com .backup sqlite3 /path/to/database.db ".backup '/backup/database_backup.db'" # Com timestamp sqlite3 /path/to/database.db ".backup '/backup/database_$(date +%Y%m%d_%H%M%S).db'" # Verificar que o backup é válido sqlite3 /backup/database_backup.db "PRAGMA integrity_check;" # Deve retornar: ok # Verificar que tem as mesmas tabelas sqlite3 /backup/database_backup.db ".tables" sqlite3 /path/to/database.db ".tables" # As tabelas devem ser idênticas
Script de hot backup
#!/bin/bash
# sqlite-hot-backup.sh — Backup consistente de SQLite
# Testado em Ubuntu 24.04, Debian 12 (2026-07)
DB_PATH="/var/www/nextcloud/data/owncloud.db"
BACKUP_DIR="/backup/sqlite"
RETENTION_DAYS=30
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/owncloud_${TIMESTAMP}.db"
# Hot backup consistente
sqlite3 "$DB_PATH" ".backup '$BACKUP_FILE'"
# Verificar integridade
RESULT=$(sqlite3 "$BACKUP_FILE" "PRAGMA integrity_check;")
if [ "$RESULT" = "ok" ]; then
echo "✅ Backup OK: $BACKUP_FILE ($(du -h "$BACKUP_FILE" | cut -f1))"
else
echo "❌ Backup CORROMPIDO: $BACKUP_FILE"
echo " Integrity check: $RESULT"
rm -f "$BACKUP_FILE"
exit 1
fi
# Comprimir para poupar espaço
gzip -f "$BACKUP_FILE"
echo "✅ Compressed: ${BACKUP_FILE}.gz"
# Rotação — apagar backups antigos
find "$BACKUP_DIR" -name "owncloud_*.db.gz" -mtime +$RETENTION_DAYS -delete
echo "✅ Old backups purged (>${RETENTION_DAYS} days)"
echo "DONE: $(date)"
4. Método 2 — Cold Backup (Cópia de Ficheiro)
Se a aplicação pode ser parada (janela de manutenção), o cold backup é o método mais simples — copiar todos os ficheiros relacionados com a base de dados:
# 1. Parar a aplicação sudo systemctl stop nextcloud # OU parar container Docker sudo docker stop nextcloud # 2. Copiar TODOS os ficheiros SQLite (.db, .db-wal, .db-shm) cp /var/www/nextcloud/data/owncloud.db /backup/owncloud_cold.db cp /var/www/nextcloud/data/owncloud.db-wal /backup/owncloud_cold.db-wal cp /var/www/nextcloud/data/owncloud.db-shm /backup/owncloud_cold.db-shm # 3. Reiniciar aplicação sudo systemctl start nextcloud # 4. Verificar backup sqlite3 /backup/owncloud_cold.db "PRAGMA integrity_check;"
ℹ O cold backup é seguro mas requer paragem de serviço. Para PMEs com janelas de manutenção noturnas, é aceitável. Para serviços 24/7, use o Método 1 (hot backup).
5. Método 3 — VACUUM INTO (Backup Otimizado)
O VACUUM INTO (disponível desde SQLite 3.27+, 2019) cria uma cópia optimizada da base de dados — sem fragmentação, sem WAL, sem páginas livres. É mais lento que .backup mas produz um ficheiro mais pequeno e optimizado:
# Backup optimizado com VACUUM INTO sqlite3 /path/to/database.db "VACUUM INTO '/backup/database_optimized.db'" # Vantagens: # - Ficheiro mais pequeno (sem páginas livres/fragmentadas) # - Sem WAL (todas as transacções são consolidated) # - Pode ser movido para outro servidor sem ficheiros auxiliares # Verificar tamanho antes/depois ls -lh /path/to/database.db # 1.2G ls -lh /backup/database_optimized.db # 800M (após remoção de fragmentação)
6. Método 4 — Backup via Docker Exec
Para aplicações em containers Docker, o backup deve ser feito dentro do container:
# Backup de SQLite dentro de container Docker
# Exemplo: Vaultwarden
CONTAINER="vaultwarden"
DB_PATH="/data/db.sqlite3"
BACKUP_DIR="/backup/vaultwarden"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
# Método A: Usar sqlite3 dentro do container (se disponível)
docker exec $CONTAINER sqlite3 $DB_PATH ".backup '/data/backup_temp.db'"
docker cp $CONTAINER:/data/backup_temp.db "$BACKUP_DIR/vaultwarden_${TIMESTAMP}.db"
docker exec $CONTAINER rm /data/backup_temp.db
# Método B: Se sqlite3 não está no container, usar docker cp + .backup externo
# Primeiro parar o container temporariamente
docker stop $CONTAINER
docker cp $CONTAINER:$DB_PATH "$BACKUP_DIR/vaultwarden_${TIMESTAMP}.db"
docker cp $CONTAINER:${DB_PATH}-wal "$BACKUP_DIR/vaultwarden_${TIMESTAMP}.db-wal" 2>/dev/null || true
docker cp $CONTAINER:${DB_PATH}-shm "$BACKUP_DIR/vaultwarden_${TIMESTAMP}.db-shm" 2>/dev/null || true
docker start $CONTAINER
# Verificar
sqlite3 "$BACKUP_DIR/vaultwarden_${TIMESTAMP}.db" "PRAGMA integrity_check;"
7. Automação com Cron — Script de Backup
# /usr/local/bin/sqlite-backup-all.sh
#!/bin/bash
# Backup de todas as bases SQLite no sistema
# Testado em Ubuntu 24.04 (2026-07)
BACKUP_ROOT="/backup/sqlite"
RETENTION_DAYS=30
LOG="/var/log/sqlite-backup.log"
echo "=== SQLite Backup $(date) ===" >> "$LOG"
# Lista de bases de dados para backup
DATABASES=(
"/var/www/nextcloud/data/owncloud.db:nextcloud"
"/var/lib/grafana/grafana.db:grafana"
"/opt/authelia/authelia.sqlite:authelia"
"/config/home-assistant_v2.db:homeassistant"
)
for entry in "${DATABASES[@]}"; do
DB_PATH="${entry%%:*}"
NAME="${entry##*:}"
BACKUP_DIR="$BACKUP_ROOT/$NAME"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/${NAME}_${TIMESTAMP}.db"
mkdir -p "$BACKUP_DIR"
if [ ! -f "$DB_PATH" ]; then
echo " ⚠ SKIP: $NAME — ficheiro não encontrado ($DB_PATH)" >> "$LOG"
continue
fi
# Hot backup
if sqlite3 "$DB_PATH" ".backup '$BACKUP_FILE'" 2>>"$LOG"; then
# Validar
CHECK=$(sqlite3 "$BACKUP_FILE" "PRAGMA integrity_check;")
if [ "$CHECK" = "ok" ]; then
gzip -f "$BACKUP_FILE"
SIZE=$(du -h "${BACKUP_FILE}.gz" | cut -f1)
echo " ✅ $NAME: ${BACKUP_FILE}.gz ($SIZE)" >> "$LOG"
else
echo " ❌ $NAME: backup corrompido ($CHECK)" >> "$LOG"
rm -f "$BACKUP_FILE"
fi
else
echo " ❌ $NAME: falha no backup" >> "$LOG"
fi
# Rotação
find "$BACKUP_DIR" -name "${NAME}_*.db.gz" -mtime +$RETENTION_DAYS -delete
done
echo "=== Done $(date) ===" >> "$LOG"
# Instalar script sudo chmod +x /usr/local/bin/sqlite-backup-all.sh # Cron — executar diariamente às 02:00 sudo crontab -e # Adicionar: 0 2 * * * /usr/local/bin/sqlite-backup-all.sh # Ou systemd timer (mais robusto) sudo tee /etc/systemd/system/sqlite-backup.timer << 'EOF' [Unit] Description=SQLite Daily Backup [Timer] OnCalendar=02:00 Persistent=true [Install] WantedBy=timers.target EOF sudo tee /etc/systemd/system/sqlite-backup.service << 'EOF' [Unit] Description=SQLite Backup Service After=network.target [Service] Type=oneshot ExecStart=/usr/local/bin/sqlite-backup-all.sh EOF sudo systemctl enable sqlite-backup.timer sudo systemctl start sqlite-backup.timer
8. Validação de Integridade — PRAGMA integrity_check
# Verificação completa de integridade sqlite3 /backup/database.db "PRAGMA integrity_check;" # Output se OK: ok # Output se erro: row X col Y: erro descrição # Verificação rápida (apenas estrutura) sqlite3 /backup/database.db "PRAGMA quick_check;" # Verificar foreign keys sqlite3 /backup/database.db "PRAGMA foreign_key_check;" # Verificar contagem de tabelas sqlite3 /backup/database.db "SELECT count(*) FROM sqlite_master WHERE type='table';" # Comparar contagem de registos entre original e backup sqlite3 /path/to/database.db "SELECT count(*) FROM users;" sqlite3 /backup/database.db "SELECT count(*) FROM users;" # Devem ser iguais # Validar schema sqlite3 /backup/database.db ".schema" > /tmp/schema_backup.txt sqlite3 /path/to/database.db ".schema" > /tmp/schema_original.txt diff /tmp/schema_original.txt /tmp/schema_backup.txt # Sem diferenças = schema idêntico
9. Retenção 3-2-1 e Rotação de Backups
A regra 3-2-1 aplica-se a backups SQLite como a qualquer outro dado:
| Regra | Aplicação SQLite |
|---|---|
| 3 cópias | Original + backup local + backup remoto |
| 2 tipos de media | Disco local + object storage (S3/Backblaze) |
| 1 cópia offsite | Sincronizar para Backblaze/Wasabi/S3 |
# Sincronizar backups para Backblaze B2 # Instalar rclone curl https://rclone.org/install.sh | sudo bash # Configurar remote rclone config # Escolher Backblaze B2, seguir prompts # Sincronizar backups rclone sync /backup/sqlite backblaze:kbase-sqlite-backups/ # Adicionar ao script de backup (após gzip): rclone sync $BACKUP_ROOT backblaze:kbase-sqlite-backups/ --transfers 4 # Rotação granular: # - Diário: 7 dias # - Semanal: 4 semanas # - Mensal: 12 meses find $BACKUP_ROOT -name "*.db.gz" -mtime +7 -delete # diários > 7 dias # Manter um semanal (não apagar backups de Domingo) find $BACKUP_ROOT -name "*.db.gz" -mtime +30 ! -name "*_Sunday*" -delete
10. Recuperação — Restaurar de Backup
# 1. Parar a aplicação sudo systemctl stop nextcloud # 2. Fazer backup do estado actual (mesmo corrompido, por segurança) cp /var/www/nextcloud/data/owncloud.db /var/www/nextcloud/data/owncloud.db.broken # 3. Descomprimir backup gunzip /backup/sqlite/nextcloud/owncloud_20260711_020000.db.gz # 4. Validar backup antes de restaurar sqlite3 /backup/sqlite/nextcloud/owncloud_20260711_020000.db "PRAGMA integrity_check;" # Deve retornar: ok # 5. Restaurar cp /backup/sqlite/nextcloud/owncloud_20260711_020000.db /var/www/nextcloud/data/owncloud.db # 6. Apagar WAL e SHM antigos (forçar rebuild) rm -f /var/www/nextcloud/data/owncloud.db-wal rm -f /var/www/nextcloud/data/owncloud.db-shm # 7. Corrigir permissões chown www-data:www-data /var/www/nextcloud/data/owncloud.db chmod 640 /var/www/nextcloud/data/owncloud.db # 8. Reiniciar aplicação sudo systemctl start nextcloud # 9. Verificar que a aplicação funciona sudo -u www-data php /var/www/nextcloud/occ status # Deve mostrar: "Nextcloud is in maintenance mode: no" e "version: X.Y.Z"
Recuperação via .restore
# Alternativa: usar .restore do SQLite CLI sqlite3 /var/www/nextcloud/data/owncloud.db ".restore '/backup/sqlite/nextcloud/owncloud_20260711.db'" # .restore substitui o conteúdo da BD actual pelo backup
11. FAQ para Sysadmins
1. Posso simplesmente copiar o ficheiro .db?
Não enquanto a aplicação está a correr. O SQLite pode estar a meio de uma transacção. Use sqlite3 database.db ".backup 'destino.db'" que é seguro e consistente.
2. Preciso de copiar os ficheiros -wal e -shm?
Com o comando .backup, não — ele lê o WAL internamente e produz um backup completo. Com cold backup (cópia directa), sim — copie os três ficheiros (.db, .db-wal, .db-shm).
3. Comprimir backups SQLite?
Sim, SQLite comprime muito bem (40-70% redução com gzip). Use gzip após validar com PRAGMA integrity_check. Para compressão máxima, use xz -6.
4. Com que frequência devo fazer backup?
Diário para a maioria das aplicações. A cada hora para bases críticas (ex: Vaultwarden, Authelia). O .backup é rápido (segundos) e não bloqueia a aplicação.
5. Como migrar SQLite para PostgreSQL?
Use sqlite3to_postgres ou o modo de exportação do SQLite: sqlite3 database.db .dump | psql -d postgres_db. Para Nextcloud, há um script de migração oficial.
6. WAL mode vs DELETE mode — afecta o backup?
WAL mode é o padrão em muitas aplicações. O .backup funciona com ambos. Em WAL mode, garantir que copia os 3 ficheiros no cold backup. Em DELETE mode, apenas o .db é necessário.
7. Como verificar se um backup está corrompido?
sqlite3 backup.db "PRAGMA integrity_check;" # ok = válido # anything else = corrompido # Contar registos sqlite3 backup.db "SELECT count(*) FROM sqlite_master;" # Deve ser > 0
8. Backups SQLite em Docker volumes?
Monte um volume para backups e use docker exec para correr o .backup dentro do container. Alternativa: use Docker backup tools como offen/docker-volume-backup que comprime e sincroniza volumes automaticamente.
12. Erros Comuns e Resoluções
| Erro | Causa | Solução |
|---|---|---|
| database is locked | Outro processo a escrever | Usar .backup (lida com locks) ou PRAGMA busy_timeout=5000 |
| PRAGMA integrity_check: row X is corrupt | Backup corrompido | Descartar backup, fazer novo; verificar SSD/SATA |
| unable to open database file | Permissões ou path errado | chown user:user backup.db && chmod 640 backup.db |
| file is encrypted or is not a database | Ficheiro não é SQLite ou é WAL sem .db | Verificar que copiou o ficheiro .db correcto |
| no such table: X | Backup incompleto ou schema errado | Re-fazer backup; comparar .schema |
| sqlite3: command not found | SQLite não instalado | sudo apt install sqlite3 |
| VACUUM INTO: syntax error | Versão SQLite antiga (< 3.27) | Actualizar SQLite ou usar .backup |
| Backup de tamanho 0 | Falha silenciosa no .backup | Verificar espaço em disco e permissões |
