--- id: task-013 title: 'Automated database backups and offsite storage' status: Done assignee: [] created_date: '2026-03-15 09:00' labels: - dev-ops - enhancement dependencies: [] priority: high --- ## Description No critical database has automated backups. No data leaves this single server. A disk failure or compromise would lose all source code (Gitea), business data (ERPNext), photos (Immich), wiki/blog content (p2p-db), and email (Mailcow). This is the highest priority infrastructure gap. ## Acceptance Criteria - [x] #1 Automated daily pg_dump for Gitea-DB (Postgres) — 3.1MB - [x] #2 Automated daily mysqldump for ERPNext MariaDB — 2.3MB - [x] #3 Automated daily mysqldump for p2p-db (MariaDB) — 707MB - [x] #4 Automated daily pg_dump for Immich Postgres — 620MB - [x] #5 Automated daily Mailcow backup (mariadb-dump) — 1.1MB - [x] #6 Off-server storage — rclone sync to Cloudflare R2 (plex-media/db-backups/) - [x] #7 Backup retention policy (7 days local, synced to R2) - [x] #8 Backup monitoring — Uptime Kuma push monitor (ID 199, 48h heartbeat interval) ## Notes ### Approach: Centralized backup container Deploy an Alpine container with cron, database clients (pg_dump, mariadb-dump), and rclone. Runs scheduled dumps for all databases, compresses, and pushes to R2. ### Template rphotos backup script at `/opt/apps/rphotos-online/backup-database.sh` has a good pattern (pg_dumpall with 30-day retention). ### Database credentials needed - Gitea-DB: Postgres, accessible via `gitea-db` container - ERPNext: MariaDB at `/opt/erpnext/` (host-only) - p2p-db: MariaDB, root password in container env - Immich: Postgres at `/opt/immich/postgres` (host bind mount) - Mailcow: MySQL in `mailcowdockerized-mysql-mailcow-1` ### R2 storage `r2-mount` container already has rclone configured for Cloudflare R2. Can reuse config for backup uploads.