Add automated PostgreSQL backup script with offsite upload #138
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
There is currently no backup automation. PostgreSQL data lives in
./data/postgres/(a host bind mount). If the VPS is lost, the disk fails, or someone runsdocker compose down -v, all family archive data is gone permanently. For a family archive storing irreplaceable scanned documents and metadata, this is the highest-consequence gap in the entire production setup.Object storage (Hetzner S3) provides built-in durability for the uploaded files — the database is the unprotected part.
What to do
1. Create
scripts/backup-db.shNightly logical backup using
pg_dump, compressed, uploaded to Hetzner Object Storage viarclone.2. Create
scripts/restore-test.shMonthly smoke test — restores the latest backup into a throwaway container and confirms it starts cleanly. A backup you have never tested is not a backup.
3. Schedule with cron on the VPS
4. Configure rclone for Hetzner Object Storage
Create a dedicated bucket
archive-db-backups(separate fromarchive-documentsused by the app).What this gives us
/var/log/familienarchiv-backup.logOut of scope for this issue
WAL-G continuous archiving (point-in-time recovery) is a further improvement but not required for v1. A nightly
pg_dumpwith offsite upload is sufficient for a family archive with low write volume.Acceptance criteria
scripts/backup-db.shruns without error on the VPS and produces a.sql.gzin/opt/backups/postgres/.archive-db-backupsbucket on Hetzner Object Storage.scripts/restore-test.shsuccessfully restores the latest backup into a throwaway container./etc/cron.d/familienarchiv-backup.Audit confirmation + scope expansion (2026-05-07)
Pre-prod audit confirms zero backup infrastructure: no backup sidecar in
docker-compose.yml, nopg_dump/wal-g/restic, no MinIO bucket versioning or replication, no documented RTO/RPO.Postgres — recommended approach
MinIO — please add to scope
The audit found MinIO is currently a single-node bind-mount with no versioning. Letters/PDFs in MinIO are irreplaceable scanned originals — losing the bucket without recovery is total data loss for the archive's central asset.
mc version enable myminio/$BUCKET— bucket versioning onmc replicate addto a second MinIO instance OR a real S3-compatible targetmc mirror --remove --watchto off-host storage (Hetzner Storage Box, B2)Off-host upload
The current title says "offsite upload" — please make this concrete. Recommendation:
restic+ Hetzner Storage Box (you already have Hetzner per #141 Tailscale issue), encrypted withRESTIC_PASSWORDfrom a separate secret.Critical AC — restore must be tested
docs/RUNBOOK.md: spin up an empty stack, run the restore script, verify a known document loads end-to-end. The audit's harshest finding is "untested backups = no backups."Tracked in audit doc as F-04 (Critical). See
docs/audits/2026-05-07-pre-prod-architectural-review.md.