Backups¶
Overview¶
Backups run daily at 02:00 via cron and use a three-tier retention strategy.
/opt/docker/backups/
├── scripts/backup.sh Cron script (source of truth: aether repo)
├── daily/ Rotated after 7 days
├── weekly/ Rotated after 28 days (copied on Sundays)
└── archive/ Kept indefinitely (RPPS snapshots)
What Gets Backed Up¶
| Data | Format | Tier | Retention |
|---|---|---|---|
aletheia_prod database |
.dump (pg_dump custom) |
daily + weekly | 7d / 28d |
aletheia_staging database |
.dump (pg_dump custom) |
daily + weekly | 7d / 28d |
umami database |
.dump (pg_dump custom) |
daily + weekly | 7d / 28d |
| Prod media files | .tar.gz (excludes annuaire/) |
daily | 7d |
| Staging media files | .tar.gz (excludes annuaire/) |
daily | 7d |
| RPPS snapshots | .tar.gz per dated folder |
archive | Indefinite |
| Encrypted secrets | .enc files in git |
Git repo | Permanent |
What is NOT backed up
- Dev environment database and media (recreatable from staging)
- Redis data (ephemeral cache + Celery broker — rebuilds on restart)
- Loki logs (7-day retention, ephemeral by design)
- Docker images (pulled from registries)
How It Works¶
Schedule, retention, and scope¶
Cron schedule:
Retention: daily = 7 days, weekly = 28 days, archive = indefinite
Databases backed up:
aletheia_prodaletheia_stagingumami
Verify the cron is installed:
Backup flow¶
- Database dumps —
pg_dump -Fc(custom format, compressed) for each database listed above - Media archives —
tar czfof prod and staging media, excluding RPPSannuaire/folders - RPPS archive — each new dated snapshot in
media/{env}/annuaire/YYYY-MM-DD/gets compressed intoarchive/rpps_{env}_{date}.tar.gz(only once, skips if archive already exists) - Weekly copy — on Sundays, the day's backups are copied to
weekly/ - Cleanup — files older than retention period are deleted. Archive is never cleaned.
File naming¶
daily/aletheia_prod_20260419_020001.dump
daily/media_aletheia_prod_20260419_020001.tar.gz
weekly/aletheia_prod_20260419_020001.dump
archive/rpps_prod_2026-04-15.tar.gz
Manual Operations¶
Trigger a backup manually¶
Output goes to stdout. The cron job appends to /var/log/backups.log.
Check backup integrity¶
# List recent backups with sizes
ls -lh /opt/docker/backups/daily/
# Verify a dump file is valid (lists TOC without restoring)
docker exec -i shared_postgres pg_restore -l < /opt/docker/backups/daily/aletheia_prod_YYYYMMDD_HHMMSS.dump
Restore a database from backup¶
# 1. Copy the dump into the container
docker cp /opt/docker/backups/daily/aletheia_prod_YYYYMMDD_HHMMSS.dump shared_postgres:/tmp/restore.dump
# 2. Drop and recreate the database (CAUTION: destroys current data)
docker exec shared_postgres psql -U admin -c \
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='aletheia_prod' AND pid <> pg_backend_pid();"
docker exec shared_postgres dropdb -U admin aletheia_prod
docker exec shared_postgres createdb -U admin -O aletheia_prod aletheia_prod
# 3. Restore
docker exec shared_postgres pg_restore -U aletheia_prod -d aletheia_prod \
-Fc --no-owner /tmp/restore.dump
# 4. Restart the app to reconnect
cd /opt/docker/aletheia/repo && make restart ENV=prod
Restore media files¶
tar xzf /opt/docker/backups/daily/media_aletheia_prod_YYYYMMDD_HHMMSS.tar.gz \
-C /opt/docker/aletheia/media/prod/
Restore an RPPS snapshot¶
# List available snapshots
ls /opt/docker/backups/archive/rpps_prod_*.tar.gz
# Extract a specific snapshot
mkdir -p /tmp/rpps_restore
tar xzf /opt/docker/backups/archive/rpps_prod_2026-04-15.tar.gz -C /tmp/rpps_restore/
Monitoring¶
- Weekly check: verify a recent backup exists and has a reasonable size (see recurring tasks)
- Disk space: backup storage grows over time — monitor with
du -sh /opt/docker/backups/ - Cron failures: check
/var/log/backups.logfor errors
Gap: Off-site backups
All backups currently stay on the same server. If the server is lost, backups
are lost too. Until off-site backups are implemented, periodically copy
/opt/docker/backups/daily/ to external storage.