Batteries

Data & Backup

Every Grit app ships a full database backup system — no add-on, no cron file to write. Manual and scheduled dumps stream straight to your object storage, you can download any of them, and — the part that actually matters — you can restore them.

The backup dashboard lives in the admin under System Hub → Data & Backup. Everything below is also available over the API and, for restore, the grit CLI.

Take a backup now

One click on Backup now creates a full snapshot. The API dumps every table, zips it, and uploads the archive to object storage (MinIO in dev; S3, R2 or B2 in production). The archive is self-describing:

backup-2026-07-21-<id>.zip
├── tables/
│ ├── users.csv
│ ├── roles.csv
│ └── … one CSV per table
├── dump.sql # INSERTs, parent → child, wrapped in a transaction
└── metadata.json # table list + per-table row counts

Backups run asynchronously — the request returns immediately with a RUNNING status, and the row flips to READY with its size once the dump finishes.

Scheduled backups

Set a schedule — daily, weekly, monthly or yearly, at a time you choose — and the scheduler picks it up on its next tick. No restart, no redeploy. Set a daily backup once and forget it; the same way the rest of Grit's batteries work: present, wired, and out of your way until you need them.

Download

Downloading a backup mints a short-lived (15-minute) pre-signed URL, so the archive comes straight from object storage and never proxies through your API. Click Download in the dashboard, or hit the endpoint:

GET /api/backups/:id/download
# → { "data": { "url": "https://…storage…/backup.zip?X-Amz-…", "expires_in": 900 } }

Restore

A backup you've never restored is a hope, not a backup. Grit ships restore as a first-class command:

# Restore into a database. Runs migrations to create the schema, then replays
# the archive's dump.sql inside a single transaction — every row lands or none does.
grit restore backup-2026-07-21-abc123.zip
# If the schema already exists, skip the migration step:
grit restore backup.zip --no-migrate

Restore clears the backed-up tables before replaying the dump, so the seeded default rows (the built-in roles) can't collide with the archive's copy of them, and the restored database matches the backup exactly. The whole replay runs in one transaction — if anything fails, nothing is left half-applied.

Restore onto the right database

The archive carries data, not schema. Point grit restore at the database you want to overwrite — it replaces the contents of the backed-up tables with the archive's. To restore into a fresh, empty database, let it run migrations first (the default); to reset an existing app to a known-good snapshot, --no-migrate skips straight to the replay.

Where backups go

Backups use the same storage layer as uploads. In development that's the bundled MinIO; in production, set STORAGE_DRIVER to s3, r2 or b2 and your archives land in your own bucket. See File Storage for provider configuration.

Use cases

  • • A nightly safety net you set once and never think about.
  • • Snapshot before a risky migration or bulk operation, so rollback is one command.
  • • Clone production data into a staging database to reproduce an issue.
  • • Point-in-time export for compliance or archival.