Backups
Carabase ships with a self-contained backup pipeline for the Postgres database. It’s the first thing you should set up before putting anything real into production.
Layout
Section titled “Layout”$CARABASE_BACKUP_DIR/ # default: ~/.carabase/backups├── dev/│ └── 2026-04-09/│ └── 2026-04-09T031700.sql.gz.enc├── staging/└── prod/ ├── 2026-04-09/ │ └── 2026-04-09T031700.sql.gz.enc └── cron.logEach file is pg_dump -Fp | gzip -9 | AES-256-GCM encrypted with the same HOST_MASTER_KEY the host uses for credential encryption. The envelope is versioned (version(1) + iv(12) + ciphertext + authTag(16)).
Scripts
Section titled “Scripts”| Script | What it does |
|---|---|
pnpm backup:<env> | Runs pg_dump, pipes through gzip → AES-256-GCM, writes .sql.gz.enc. Refuses to run without HOST_MASTER_KEY |
pnpm backup:prune <env> | Retention: keep every backup from the last 7 days, the newest from each of the prior 4 weeks, the newest from each of the prior 3 months |
pnpm backup:status | Reports the age of the newest backup per env. PASS < 30h, WARN 30h..72h, FAIL ≥ 72h |
pnpm backup:restore <env> <file> | Decrypts into a chmod-600 tempfile, prompts for restore confirmation, applies via psql --single-transaction --set ON_ERROR_STOP=on |
pnpm backup:install-cron <env> | Writes a launchd plist that runs backup + prune nightly at 03:17 local |
Daily workflow
Section titled “Daily workflow”# One-time: install the cron for prod on the always-on hostpnpm backup:install-cron prod
# Any time, check healthpnpm backup:status# → [dev ] PASS latest=2026-04-09T03:17:12Z age=7.2h ago# [staging] PASS latest=2026-04-09T03:17:14Z age=7.2h ago# [prod ] PASS latest=2026-04-09T03:17:18Z age=7.2h ago
# Restore a prod dump into dev for debuggingpnpm backup:restore dev ~/.carabase/backups/prod/2026-04-09/2026-04-09T031700.sql.gz.encProduction guardrail
Section titled “Production guardrail”backup:restore refuses to restore into prod unless the env arg is prod AND --i-know is passed. This is the same friction pattern as db:reset — typing CARABASE_I_KNOW=1 is what proves you mean it.
Restoring on a different machine
Section titled “Restoring on a different machine”A backup is just a .sql.gz.enc file — portable. Copy it plus the same HOST_MASTER_KEY to the target machine, then run ./scripts/restore-db.sh <env> <file>. Without the master key, the file is worthless; never lose the key separately from the backups.
What’s still on the roadmap
Section titled “What’s still on the roadmap”- Off-machine cloud sink (Backblaze B2 / iCloud Drive) — Phase 5
/api/v1/healthdetail surfacesbackup:statusso the desktop can show a “backup stale” warning- PITR / WAL archiving for the prod Postgres instance (overkill for single-tenant use today; revisit if the dataset grows)