Runbook — Restore gamine_cms Database from Backup¶
Scope: gamine-cms project — Wagtail CMS for gamine.org
Frequency: On incident (accidental drop, corruption, failed migration)
Estimated time: 15–30 min depending on backup size
Prerequisites¶
- SSH access to VPS (
ssh vps) - Backup file at
/opt/backups/postgres/gamine_cms_<DATE>.sql.gz - gamine-cms container running (or stopped, depending on step)
litellmPostgreSQL user credentials (from Vaultwarden → Infrastructure → PostgreSQL)
Step 1 — Identify the correct backup¶
ls -lh /opt/backups/postgres/gamine_cms_*.sql.gz | tail -10
# Pick the most recent clean backup — check size (healthy backup is > 50KB)
du -h /opt/backups/postgres/gamine_cms_2026-05-21.sql.gz
⚠️ If all backups are tiny (< 1KB), the DB was never populated or backup.sh was broken. Check
/opt/backups/postgres/for the newest non-empty file.
Step 2 — Stop gamine-cms to prevent writes during restore¶
cd /opt/rabbithall-compose # or wherever gamine-cms compose lives when deployed
docker compose stop gamine-cms
Step 3 — Drop and recreate the database¶
docker exec infra-postgres psql -U litellm -d postgres -c "
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = 'gamine_cms' AND pid <> pg_backend_pid();
"
docker exec infra-postgres psql -U litellm -d postgres -c "DROP DATABASE IF EXISTS gamine_cms;"
docker exec infra-postgres psql -U litellm -d postgres -c "CREATE DATABASE gamine_cms OWNER litellm;"
Step 4 — Restore from backup¶
BACKUP=/opt/backups/postgres/gamine_cms_2026-05-21.sql.gz # adjust date
gunzip -c "$BACKUP" | docker exec -i infra-postgres psql -U litellm -d gamine_cms
Verify row counts after restore:
docker exec infra-postgres psql -U litellm -d gamine_cms -c "
SELECT schemaname, tablename, n_live_tup
FROM pg_stat_user_tables
ORDER BY n_live_tup DESC
LIMIT 20;
"
Step 5 — Run pending Django migrations (if restoring to older schema)¶
cd /opt/gamine-cms # when deployed
docker compose run --rm gamine-cms python manage.py migrate --run-syncdb
Step 6 — Restart and verify¶
docker compose start gamine-cms
sleep 5
curl -s https://gamine.org/health/ | python3 -m json.tool
# Expected: {"status": "ok", "version": "x.y.z", "db": "ok"}
Check Wagtail admin is accessible:
curl -o /dev/null -sw "%{http_code}" https://gamine.org/cms-admin/
# Expected: 200 or 302 (redirect to login)
Step 7 — Notify¶
Send Telegram alert confirming restore complete:
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d chat_id="${TELEGRAM_CHAT_ID}" \
-d text="✅ gamine_cms DB restored from backup $(date -u +%Y-%m-%dT%H:%M)Z. Verify: https://gamine.org"
Rollback procedure (if restore made things worse)¶
If the restore fails partway through and the DB is in an inconsistent state:
- Drop
gamine_cmsagain (Step 3) - Try the next-oldest backup
- If all backups are bad → escalate to João; run
python manage.py migrate --fake-initialfrom a fresh DB and re-import content from gamine-brain snapshots
Related¶
backup.shat/opt/backup.sh— runs daily, keeps 30 days of postgres dumps- Vikunja #665 — this runbook
- ADR-006 (
adr-006-wagtail-for-gamine-cms.md) — why Wagtail was chosen - Wagtail docs: https://docs.wagtail.org/en/stable/