Raised: $0
0% of monthly goal Help us cross the finish line!
Goal: $12,000
Raised: $0 Goal: $12,000
0% of monthly goal Help us cross the finish line!
Sponsor DDEV

If you find this add-on useful, please star it on GitHub — stars show appreciation and help maintainers know their work matters.

add-on registry tests last commit release

ddev-backupdrill

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.

Why drill a local database?

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.

Requirements

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.

Installation

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.

Usage

ddev start
ddev backupdrill

What it does:

  1. Starts an S3-compatible bucket in a container (versitygw), listening on 127.0.0.1 on a random port, with random keys.
  2. Runs 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.
  3. Runs 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.
  4. Removes the temporary bucket, and the sandbox too unless the drill failed and you passed --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.

Drill flags

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.

What it reads, and what it ignores

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:

Limitations

Backing up a real database

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.

Removal

ddev add-on remove backupdrill

Credits

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.