If you find this add-on useful, please star it on GitHub — stars show appreciation and help maintainers know their work matters.
ddev backupdrill backs up your project’s Postgres database, restores the backup into a throwaway Postgres container, and checks that the tables and rows came back.
It runs the open-source backupdrill CLI. That’s the engine BackupDrill uses for its scheduled restore drills on Supabase projects.
Your DDEV database usually has the same schema as production. Running a drill on it shows whether that schema survives a pg_dump and pg_restore round trip (extensions, views, foreign keys, indexes) before you trust dumps of the real thing. It’s also a quick way to see what a restore drill checks and what the report looks like.
It doesn’t keep backups of your dev database. The snapshot goes into a temporary bucket that is deleted when the command exits. Use ddev snapshot or ddev export-db if you want a copy to keep.
ddev config --database=postgres:17npxpg_dump and pg_restore with a major version at least as high as the project’s Postgres. On macOS, brew install libpq and add $(brew --prefix libpq)/bin to your PATH. On Debian or Ubuntu, sudo apt install postgresql-client. If they live somewhere else, point BACKUPDRILL_PG_DUMP and BACKUPDRILL_PG_RESTORE at them.The command runs on the host because the drill starts its sandbox with docker run and connects to it on 127.0.0.1. The web container can’t do either.
ddev add-on get backupdrill/ddev-backupdrill
The add-on refuses to install on a MySQL or MariaDB project. Commit the .ddev directory so everyone on the project gets the command.
ddev start
ddev backupdrill
What it does:
127.0.0.1 on a random port, with random keys.backupdrill backup, which dumps the project database (the public schema) into that bucket along with a manifest: tables, estimated row counts, and the dump’s sha256.backupdrill drill, which downloads the dump, checks the sha256, restores it into a fresh postgres:<major>-alpine container, and compares what came back against the manifest.--keep. What Ctrl+C leaves behind is under Limitations.Output from a project with four tables and about 200,000 rows (some progress lines left out):
✓ Postgres 17.11 (Debian 17.11-1.pgdg13+2) — schema(s) public: 4 tables, ~201,254 rows
✓ Database dumped (1.3 MB, sha256 23b232b791f3…)
✓ Backup complete — 4 tables (database only).
→ Drilling snapshot 2026-10-22T11-12-55-623Z
✓ archive integrity — sha256 matches (23b232b791f3…)
✓ sandbox extensions — installed pg_trgm
✓ pg_restore — completed in 0.4s (2 parallel jobs)
✓ post-data objects — all post-data objects restored
✓ table count — restored 4, manifest 4
✓ no missing tables — all manifest tables present
✓ populated tables came back — no populated table restored empty
✓ Drill PASSED — snapshot 2026-10-22T11-12-55-623Z, 4 tables / 201,254 rows restored in 0.4s
Removed the temporary bucket. The snapshot was deleted with it; nothing is kept.
The exit code is 0 when the drill passes and non-zero when it doesn’t. The full drill report also goes to stdout as one line of JSON, if you want to parse it in a script.
Two flags are passed through to backupdrill drill:
| Flag | What it does |
|---|---|
--check-cmd <command> |
After the built-in checks pass, runs your own command against the restored copy, with its connection string in BACKUPDRILL_SANDBOX_URL. Exit 0 passes, anything else fails the drill. |
--keep |
If the drill fails, leaves the sandbox container running so you can look inside. The output tells you how to remove it. |
ddev backupdrill --check-cmd ./scripts/check-restore.sh
The check command runs on your machine from the project root, not in the web container. It connects as the sandbox’s superuser, so row-level security doesn’t apply. Use it for data checks (“every order has a customer”, “this table isn’t empty”), not to test RLS policies.
Any other flag is refused. Options like --config, --bucket or --ping-url would point the CLI at real storage or report a local result to your production monitoring, which this command is built never to do.
The command only ever talks to the project database and its own temporary bucket. It doesn’t read a backupdrill.config.json in the project, and it drops every BACKUPDRILL_* variable and DATABASE_URL from your shell before it starts the CLI, so a production setting you exported earlier can’t redirect it. Three variables are kept:
BACKUPDRILL_PG_DUMP and BACKUPDRILL_PG_RESTORE: paths to the Postgres client toolsBACKUPDRILL_SCHEMAS: schemas to dump instead of public, comma-separatedpg_dump and pg_restore and has no MySQL or MariaDB support.public schema unless you set BACKUPDRILL_SCHEMAS.backupdrill@2 (the newest 2.x release), and the first run pulls the versitygw and Postgres images, so it needs network access and takes longer.--check-cmd carries on until it finishes. The CLI doesn’t clean up after itself when it’s interrupted yet. List leftover sandboxes with docker ps --filter ancestor=postgres:17-alpine (use your Postgres version) and remove one with docker rm -fv <id>.To back up a production database to your own S3, R2 or B2 bucket, run the backupdrill CLI from npm directly. It does the same backup and drill shown here, and for a Supabase project it also copies the Storage files once you give it the project’s Storage S3 keys. If you don’t want to run it yourself, BackupDrill does it on a schedule for Supabase projects and emails you when a backup or drill fails.
ddev add-on remove backupdrill
Contributed and maintained by @backupdrill
If you find this add-on useful, please star it on GitHub — stars show appreciation and help maintainers know their work matters.