Skip to content

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.

$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.log

Each 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)).

ScriptWhat 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:statusReports 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
Terminal window
# One-time: install the cron for prod on the always-on host
pnpm backup:install-cron prod
# Any time, check health
pnpm 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 debugging
pnpm backup:restore dev ~/.carabase/backups/prod/2026-04-09/2026-04-09T031700.sql.gz.enc

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.

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.

  • Off-machine cloud sink (Backblaze B2 / iCloud Drive) — Phase 5
  • /api/v1/health detail surfaces backup:status so 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)