08-database-backups-migrations.md
markdown
sha256:7d5985ef251de9f0154f9b185a75cf36174bf73e0c34f5eca133e7bd20224bf4
Merge branch 'feat/opengraph-repo-cards' into dev
Human
23 days ago
Section 8 — Database Security, Migrations, and Backups
Companion to
musehub-production-readiness-checklist.md.
Backups — verified live on staging via SSM: none exist
Checked directly on the staging instance:
crontab -l → "no crontab for root" # backup.sh's cron job is NOT installed
/opt/backups/musehub/ → one file:
musehub_staging_manual_20260613_003724.sql.gz (20 bytes, ~2 months old)
which rclone → not found
One manual backup exists, it's ~2 months stale, and it's 20 bytes — almost certainly an empty
or failed dump, not a usable backup. rclone (required for the R2 off-disk tier in backup.sh)
isn't installed. This confirms docs/infrastructure.md's "No automated backup is configured yet"
for the backup path specifically (unlike object storage, which turned out to be live — see
Section 0). There is currently no usable backup of the production database anywhere.
Database security
- [x] Keep the database off the public internet. — confirmed (Section 5), no public exposure
- [ ] Encryption at rest / in transit — not verified in this pass (self-hosted Postgres in a Docker container; would need to check the container's TLS config and the underlying EBS volume encryption setting directly)
- [ ] Separate roles for application runtime / migrations / read-only / administration —
not done. A single
musehubPostgres role is used for everything: app queries, Alembic migrations, and adminpsqlaccess (perdocs/infrastructure.md's documentedpsqlcommand). This is a real gap worth closing before launch — migrations running with the same role as request-serving traffic means an app-level SQL injection would have migration-level privileges. - [ ] Deletion protection — not enabled (this is an EC2+Docker Postgres, not RDS, so "deletion protection" means something different here: protecting the EBS volume/snapshot lifecycle rather than an RDS API flag — not configured either way)
- [ ] Audit logging — not enabled
- [ ] Monitoring (storage/CPU/memory/connections/locks/slow queries) — partially:
slow_query_threshold_msexists inmusehub/config.py(app-level slow-query logging at the ORM layer), but there's no infrastructure-level DB monitoring (no CloudWatch Agent metrics confirmed, Section 0)
Migrations — mostly solid, one real risk found
- 75 Alembic migrations, run automatically as an explicit deploy step, before the blue/green slot swap (forward-compatible ordering — correct).
- Schema-parity gate exists and hard-fails the deploy:
musehub.db.schema_gatecompares live schema against ORM models after migrations run, filtering out benign diffs (alembic_version table, equivalent server defaults, column comments) and failing the deploy on real drift. This is a genuinely good safety net — better than most self-hosted setups have. - ⚠️ Failure-handling risk: if
alembic upgrade headfails,deploy.shresponds by runningalembic stamp --purge head(discards Alembic's revision history and force-marks the DB as being at head) and retrying — with a comment claiming this is a safe no-op "when the schema already matches head." But the script doesn't distinguish why upgrade failed — a stale revision ID (the stated intent) and a genuinely broken migration look the same to this logic. The follow-upschema_gatehard-fail is the real safety net here, not the stamp-and-retry itself — worth explicitly documenting that reliance, since ifschema_gateever had a false negative, a broken migration could silently ship. - [ ] Only one migration runner executes at a time — not independently verified (single deploy process today, so unlikely to collide, but no explicit lock mechanism confirmed)
- [ ] Back up before high-risk migrations — moot until backups exist at all
- [ ] Expand-and-contract discipline — not reviewed across the 75 migrations in this pass
- [ ] Rollback/forward-repair procedures — not documented
- [ ] Record migration state/duration, alert on failure — not done (no alerting exists, Section 12)
- [x] Older/newer app versions coexisting during blue/green — the pipeline's ordering (migrate before starting the new slot, old slot still serving on the old code during migration) implies this is intentional, but expand-and-contract discipline (the actual guarantee) isn't verified
The actual work items here
- Get a real backup running — install
rclone, configure the R2 remote, install thebackup.shcron job, and verify a real (non-empty, current) backup lands both locally and in R2. This is the single highest-priority item in this whole checklist — there is currently zero recovery path for the production database. - Perform an actual restore test once backups exist — checklist explicitly requires this before launch, not just "backups are running."
- Split the Postgres role — at minimum, separate migration/admin access from the app's runtime role.
- Define RPO/RTO (Section 0, still open) so backup cadence and retention can be sized correctly rather than guessed.
File History
1 commit
sha256:be1b2fdf6ab1fc652b3b35a945d0277a36fbf5d96e63692020d36cd7ebe03534
Merge 'docs/security-monitoring-verified' into 'dev' — prop…
Human
3 days ago