The backupdrill CLI
An open-source (MIT) CLI that backs up a Supabase project — the Postgres database and your Storage files — to your own S3, R2, or B2 bucket, with a checksummed manifest.json so you can prove what was captured. Backups stream from your machine or CI straight to your bucket; there are no servers of ours in the path. Source and issues at github.com/backupdrill/cli.
Requirements
Node.js ≥ 20, and a pg_dump whose major version is ≥ your Supabase Postgres version (Supabase runs PG 15–17). On macOS: brew install libpq and add it to PATH, or point BACKUPDRILL_PG_DUMP at the binary. The CLI checks this and refuses to run a mismatched dump.
Install
npm install -g backupdrill
# or run without installing:
npx backupdrill backupConfigure
Configure via env vars, a config file, or flags (precedence: flags > env > file). Use the Session pooler connection string from the Connect button at the top of the Supabase dashboard — the postgres user in it works as-is. A read-only backup_reader role is an advanced option that only works if your schema has no RLS-enabled tables — see the advanced setup in the quickstart.
export BACKUPDRILL_DATABASE_URL="postgresql://postgres.<ref>:<pw>@aws-0-<region>.pooler.supabase.com:5432/postgres"
export BACKUPDRILL_PROJECT_NAME="my-app"
export BACKUPDRILL_S3_ENDPOINT="https://<account>.r2.cloudflarestorage.com" # omit for AWS S3
export BACKUPDRILL_S3_REGION="auto"
export BACKUPDRILL_S3_BUCKET="my-backups"
export BACKUPDRILL_S3_ACCESS_KEY_ID="…"
export BACKUPDRILL_S3_SECRET_ACCESS_KEY="…"To also capture Storage files (a database restore alone only brings back storage.objects metadata, not the files), enable S3 access under Storage → Settings → S3 Access Keys in the Supabase dashboard and add:
export BACKUPDRILL_SUPABASE_STORAGE_ENDPOINT="https://<ref>.storage.supabase.co/storage/v1/s3"
export BACKUPDRILL_SUPABASE_STORAGE_REGION="<project-region>"
export BACKUPDRILL_SUPABASE_STORAGE_ACCESS_KEY_ID="…"
export BACKUPDRILL_SUPABASE_STORAGE_SECRET_ACCESS_KEY="…"
# optional: only specific buckets
export BACKUPDRILL_SUPABASE_STORAGE_BUCKETS="avatars,uploads"Commands
backupdrill backup # dump DB (+ Storage files if configured) to your bucket
backupdrill estimate --plan pro # project your monthly Supabase egress cost before scheduling
backupdrill drill # restore latest snapshot into a throwaway Postgres (needs
# Docker) and verify it; --snapshot <timestamp> to pick one,
# --verify-all-files to checksum every Storage file
backupdrill drill \
--check-cmd "npm run smoke" # v0.2: after structural checks pass, run YOUR smoke test
# against the restored copy (BACKUPDRILL_SANDBOX_URL is set;
# exit 0 = pass, reported as 'app checks'); --keep holds the
# sandbox open on failure for inspection
backupdrill restore \
--target-database-url "postgresql://…/postgres" \
--storage-dir ./recovered # real recovery: DB into a target, files to a local folderFor the full recovery playbook — including a raw pg_restore fallback — see Restore & recovery.
Egress cost — read before scheduling daily backups
A backup pulls data out of Supabase, which is uncached egress on your Supabase bill: $0.09/GB above your plan’s allowance (Free 5 GB, Pro/Team 250 GB per month). Spend Cap is on by default on Pro/Team — blow past the quota and Supabase throttles your whole project’s egress for the rest of the month and the backup fails. Free is a hard cap: a large daily backup can’t complete on it. Rough cost on Pro with Spend Cap off, backups as your only egress:
| DB + Storage | Weekly | Daily |
|---|---|---|
| 10 GB | $0/month (within 250 GB) | ~$4.50/month |
| 50 GB | ~$0/month | ~$112/month |
Cost scales with size × frequency. Run backupdrill estimate for your own numbers, and prefer weekly once your data is large.
Schedule it with GitHub Actions
The repo ships a workflow template at examples/scheduled-backup.yml — copy it into your own repo as .github/workflows/scheduled-backup.yml. It runs daily (or on demand from the Actions tab) and installs a matching pg_dump for you. Add these repo secrets: BACKUPDRILL_DATABASE_URL, BACKUPDRILL_S3_BUCKET, BACKUPDRILL_S3_ACCESS_KEY_ID, and BACKUPDRILL_S3_SECRET_ACCESS_KEY; the endpoint, region, project name, and Storage-file variables are optional. Read the egress section above before going daily.
The hosted service runs this same engine — with scheduling, restore drills, failure alerts, and reports, so nothing here depends on you remembering to run it. Open the console or start with the quickstart.