Restore: mrrmlabapp PostgreSQL-DB
Backup- und Restore-Pfad für die mrrmlabapp-Produktions-DB auf prod-alt.
Backup-Setup
- Tool:
prodrigestivill/postgres-backup-local:17-alpinealsdb-backup-Service immrrmlabapp-Stack - Schedule:
@daily(00:00, env:MRRMLABAPP_BACKUP_SCHEDULE) - Format:
pg_dump | gzip→mrrmlabapp-YYYYMMDD.sql.gz - Volume:
mrrmlabapp_mrrmlabapp-db-backups→ gemountet auf/backupsim Container - Retention (env-konfigurierbar):
BACKUP_KEEP_DAYS=7(täglich)BACKUP_KEEP_WEEKS=4(wöchentlich)BACKUP_KEEP_MONTHS=6(monatlich)
- Healthcheck-Port: 8080 (Container-intern); fail wenn letztes Backup älter als erwartet
- Restic: Volume
/var/lib/docker/volumes/mrrmlabapp_mrrmlabapp-db-backups/_dataist innas_backup_dirs(prod-alt) → tägliche restic-Sync auf QNAP - Monitoring: Uptime-Kuma-Push-Monitor
mrrmlabapp-db-backup-prodaufstatus.mrrm.de(vault_mrrmlabapp_backup_webhook_url→MRRMLABAPP_BACKUP_WEBHOOK_URL); Container pingt nach jedem erfolgreichen Dump, Kuma alerted bei Ausbleiben (heartbeat 86400s, retry 300s) - Wichtig:
WEBHOOK_EXTRA_ARGS=-X GETsetzen, weilprodrigestivill/postgres-backup-local's/hooks/00-webhookhartcodiert--request POSTmacht, Kuma-Push aber GET erwartet
Backup-Schichten (Defense in Depth)
prod-alt postgres → db-backup Container → /backups Volume (lokal)
↓
restic → QNAP-NAS (offsite-ish)
Restore-Test (Hash-Verify)
Verifiziert das Backup ohne Live-Stack zu berühren. Läuft auf dev-neu gegen den prod-alt-Snapshot.
Achtung: Das QNAP-NAS ist tagsüber ausgeschaltet (siehe Infrastruktur#Backup--Restore). Restore-Tests nur im Nacht-Fenster (ab ca. 03:30) oder NAS manuell hochfahren.
# 1. Neuesten prod-alt-Snapshot mit DB-Backups finden
. /etc/restic.env
SNAP=$(resticq snapshots \
--host prod-alt \
--path /var/lib/docker/volumes/mrrmlabapp_mrrmlabapp-db-backups/_data \
--json 2>/dev/null \
| python3 -c 'import json,sys; print(json.load(sys.stdin)[-1]["id"])')
echo "Snapshot: $SNAP"
# 2. Restore in Temp-Dir
TARGET=/tmp/restore-test-mrrmlabapp-db-$(date +%Y%m%d-%H%M%S)
resticq restore "$SNAP" \
--target "$TARGET" \
--include /var/lib/docker/volumes/mrrmlabapp_mrrmlabapp-db-backups/_data
BACKUP_DIR="$TARGET/var/lib/docker/volumes/mrrmlabapp_mrrmlabapp-db-backups/_data"
LATEST=$(ls -t "$BACKUP_DIR/daily/"*.sql.gz | head -1)
echo "Neuestes Dump: $LATEST ($(du -h "$LATEST" | cut -f1))"
# 3. Dump-Integrität: gzip + sha256
gunzip -t "$LATEST" && echo "✓ gzip intakt"
sha256sum "$LATEST"
# 4. Lade in temporären Postgres-Container
NET=$(docker network ls --filter name=mrrmlabapp -q | head -1) # reuse Postgres-Netz falls vorhanden, sonst eigenes
docker run -d --name pg-restore-test \
-e POSTGRES_PASSWORD=test \
-e POSTGRES_DB=mrrmlabapp_restore \
postgres:17-alpine
sleep 5
gunzip -c "$LATEST" | docker exec -i pg-restore-test \
psql -U postgres -d mrrmlabapp_restore -v ON_ERROR_STOP=1 2>&1 | tail -5
# 5. Verifikations-Queries
docker exec pg-restore-test psql -U postgres -d mrrmlabapp_restore -t -c "
SELECT 'tables: ' || count(*) FROM pg_tables WHERE schemaname='public';
SELECT 'lists: ' || count(*) FROM lists;
SELECT 'list_items: ' || count(*) FROM list_items;
SELECT 'stores: ' || count(*) FROM stores;
SELECT 'mail_accounts: ' || count(*) FROM mail_accounts;
SELECT 'parcel_trackings: ' || count(*) FROM parcel_trackings;
"
# 6. Cleanup
docker rm -f pg-restore-test
rm -rf "$TARGET"
Erwartung: ≥10 Tabellen (lists, list_items, mail_accounts, mail_folders, mail_messages_cache, mail_tags, stores, order_infos, parcel_trackings, drizzle-Migrations-Meta), Row-Counts > 0 für aktive Tabellen.
Letzte erfolgreiche Restore-Tests
| Datum | Snapshot | Dump-Datum | Tabellen | Lists/Items | Ergebnis |
|---|---|---|---|---|---|
| (noch ausstehend, NAS-Fenster abwarten) | — | — | — | — | — |
Echter Restore in Produktion
Wenn die prod-alt-DB verloren geht:
-
API-Container stoppen (damit nichts schreibt):
cd /opt/server-stack/hosts/prod-alt/mrrmlabapp docker compose stop api -
Neuesten Dump aus restic restoren (NAS-Fenster!):
. /etc/restic.env SNAP=$(resticq snapshots --host prod-alt \ --path /var/lib/docker/volumes/mrrmlabapp_mrrmlabapp-db-backups/_data \ --json | python3 -c 'import json,sys; print(json.load(sys.stdin)[-1]["id"])') resticq restore "$SNAP" --target /tmp/db-restore \ --include /var/lib/docker/volumes/mrrmlabapp_mrrmlabapp-db-backups/_data LATEST=$(ls -t /tmp/db-restore/var/lib/docker/volumes/mrrmlabapp_mrrmlabapp-db-backups/_data/daily/*.sql.gz | head -1) -
DB droppen + neu anlegen (Vorsicht, destruktiv!):
docker exec mrrmlabapp-db psql -U mrrmlabapp -d postgres -c "DROP DATABASE mrrmlabapp;" docker exec mrrmlabapp-db psql -U mrrmlabapp -d postgres -c "CREATE DATABASE mrrmlabapp;" -
Dump einspielen:
gunzip -c "$LATEST" | docker exec -i mrrmlabapp-db psql -U mrrmlabapp -d mrrmlabapp -
API wieder starten + verifizieren:
docker compose start api docker compose logs -f api # auf Migrations + Boot-Errors achten
Referenzen
- Issue: #201 (DB-Backup-Strategie)
- Verwandt: Restore-Paperless
- Compose:
stacks/prod-alt/mrrmlabapp/docker-compose.yml(Servicedb-backup) - Image-Doku: https://github.com/prodrigestivill/docker-postgres-backup-local