Apéndice E — Backup y disaster recovery¶
E.1. Backups diarios¶
/opt/tienda/scripts/backup_daily.sh:
#!/usr/bin/env bash
set -euo pipefail
DATE=$(date +%F)
BACKUP_DIR=/opt/tienda/backups
S3_BUCKET=s3://erp-backups-tuempresa
mkdir -p "$BACKUP_DIR/$DATE"
sudo -u postgres pg_dump -Fc tienda_db | gzip -9 > "$BACKUP_DIR/$DATE/tienda_db.dump.gz"
tar czf "$BACKUP_DIR/$DATE/media.tar.gz" /opt/tienda/media/
tar cz /opt/tienda/secrets /opt/tienda/.env | \
gpg --batch --yes --symmetric --cipher-algo AES256 \
--passphrase-file /root/.gpg_backup_pass \
--output "$BACKUP_DIR/$DATE/secrets.tar.gz.gpg"
aws s3 sync "$BACKUP_DIR/$DATE/" "$S3_BUCKET/$DATE/" --storage-class STANDARD_IA
find "$BACKUP_DIR" -maxdepth 1 -type d -name "20*" -mtime +30 -exec rm -rf {} +
Cron: 0 2 * * * /opt/tienda/scripts/backup_daily.sh >> /var/log/backup.log 2>&1
Buena práctica: probar el restore 1 vez al mes en VPS de staging.
E.2. Procedimiento de restore¶
aws s3 sync s3://erp-backups-tuempresa/2026-05-14/ /tmp/restore/
systemctl stop tienda tienda-daphne tienda-celery tienda-celerybeat
sudo -u postgres dropdb tienda_db
sudo -u postgres createdb tienda_db -O tienda
gunzip -c /tmp/restore/tienda_db.dump.gz | sudo -u postgres pg_restore -d tienda_db
tar xzf /tmp/restore/media.tar.gz -C /
gpg --decrypt --passphrase-file /root/.gpg_backup_pass /tmp/restore/secrets.tar.gz.gpg | tar xz -C /
cd /opt/tienda && set -a && . .env && set +a && .venv/bin/python manage.py check
systemctl start postgresql redis-server tienda tienda-daphne tienda-celery tienda-celerybeat
RTO objetivo: 30 min. RPO objetivo: 24h (bajar a 1h con WAL archiving).
E.3. Réplica caliente Postgres (opcional)¶
# postgresql.conf primario
wal_level = replica
max_wal_senders = 3
archive_mode = on
archive_command = 'aws s3 cp %p s3://erp-wal-archive/%f'
Failover con pg_promote() o automático con Patroni (ver Apéndice M).