Skip to content

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).

Terminal window
docker compose exec backup ls -lh /backups # see your backups
docker compose exec backup sh /backup.sh once # take one right now

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):

Terminal window
docker compose exec backup createdb restore_drill
docker 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 there
docker compose exec backup dropdb restore_drill

Real restore (replaces live data — stop the app first):

Terminal window
docker compose stop web scheduler
docker compose exec backup sh -c 'pg_restore -d $POSTGRES_DB --clean --if-exists --no-owner /backups/<FILE>.dump'
docker compose start web scheduler
curl -s https://api.your-domain.com/health/
  1. New VPS (either runbook, steps 1–5).
  2. Copy your .env (you kept a safe copy — right? If not: a new secret key invalidates all sessions/tokens, but your data is fine).
  3. docker compose up -d, wait healthy.
  4. Restore the latest dump from your S3 bucket (real-restore steps above).
  5. Re-point DNS at the new IP.

Related: Upgrading (take a backup before every upgrade) · Hardening.