Backups
Automatic, out of the box: the backup container runs pg_dump nightly
(03:00 UTC, configurable via BACKUP_HOUR) into a named Docker volume, in
PostgreSQL’s compressed custom format, keeping 7 days (BACKUP_KEEP_DAYS).
docker compose exec backup ls -lh /backups # see your backupsdocker compose exec backup sh /backup.sh once # take one right nowRestoring a backup
Section titled “Restoring a backup”Practice this once on day one — a backup you’ve never restored is a hope, not a backup. (The shipped test suite does exactly this drill in CI.)
Drill (into a scratch database, zero risk):
docker compose exec backup createdb restore_drilldocker compose exec backup sh -c 'pg_restore -d restore_drill --no-owner /backups/<FILE>.dump'docker compose exec backup psql -d restore_drill -c '\dt' | head # tables are theredocker compose exec backup dropdb restore_drillReal restore (replaces live data — stop the app first):
docker compose stop web schedulerdocker compose exec backup sh -c 'pg_restore -d $POSTGRES_DB --clean --if-exists --no-owner /backups/<FILE>.dump'docker compose start web schedulercurl -s https://api.your-domain.com/health/Disaster recovery, summarized
Section titled “Disaster recovery, summarized”- New VPS (either runbook, steps 1–5).
- Copy your
.env(you kept a safe copy — right? If not: a new secret key invalidates all sessions/tokens, but your data is fine). docker compose up -d, wait healthy.- Restore the latest dump from your S3 bucket (real-restore steps above).
- Re-point DNS at the new IP.
Related: Upgrading (take a backup before every upgrade) · Hardening.