Postgres backups, restore-tested · checked

A Postgres backup that is restored and checked every time

Supabase's Free plan keeps no backups, Neon's Free plan keeps a 6-hour restore window, and each provider's own GitHub Actions recipe stops at pg_dump, without restoring anything. restoreproof backs your database up every week in your own GitHub Actions, restores the backup into a throwaway PostgreSQL of the same version, and compares every table, column, policy and row. When the backup would not restore, the job fails. Nothing is hosted by us, and nothing is sent to us. Until its repository is published, the commands below run the same test by hand.

Test a backup by hand

Set DATABASE_URL to the database's connection string. Run these where PostgreSQL's own programs are installed, of the server's version or newer; they need nothing from us and send nothing anywhere.

# 1. back up
pg_dump --format=custom --no-owner --no-privileges --file=backup.dump "$DATABASE_URL"

# 2. restore into a scratch database on a local PostgreSQL of the same major version
createdb restore_test
pg_restore --no-owner --no-privileges --exit-on-error --dbname=restore_test backup.dump

# 3. count every table on both sides, and compare
COUNT="SELECT format('SELECT %L, count(*) FROM %I.%I;', schemaname || '.' || relname, schemaname, relname) FROM pg_stat_user_tables ORDER BY 1"
psql "$DATABASE_URL" -XAtc "$COUNT" | psql "$DATABASE_URL" -XAt > live.txt
psql -d restore_test -XAtc "$COUNT" | psql -d restore_test -XAt > restored.txt
diff live.txt restored.txt && echo "every table came back with the same number of rows"

--exit-on-error makes the restore stop at its first error instead of carrying on past it. A role that a row-level security policy names must exist first (createuser --no-login <role>), and so must every extension the backup creates (pgvector and PostGIS are packages of their own). The counts can differ by the rows written while the backup ran. This compares rows only; restoreproof also compares every column, constraint, index, function, trigger and policy.

What each run checks

A recorded run

On a synthetic database shaped like a small Supabase project (auth and storage schemas, policies calling auth.uid(), a partitioned table), recorded 9 Oct 2026. Not anyone's data.

restoreproof 0.1.0 · restore test PASSED · 2026-10-09 20:37 UTC

  source    PostgreSQL 17.9 (supabase), 9.1 MB, read through RESTOREPROOF_DATABASE_URL
  backup    91.5 KB in 0.1 s (pg_dump 17.9, custom format)
  restored  in 0.1 s into PostgreSQL 17.9, a throwaway cluster on 127.0.0.1 with no other connections
  checked   5,925 rows in 9 tables against the live database: all match
            92 schema objects: 11 tables, 37 columns, 17 constraints, 14 indexes, 1 view, 1
            materialized view, 2 functions, 1 trigger, 3 policies, 3 sequences, 1 enum, 1 domain

  table                                   live → restored
  auth.users                               400 → 400
  public.events_2026_09                  1,500 → 1,500
  public.events_2026_10                  1,500 → 1,500
  public.notes                           2,000 → 2,000
  public.profiles                          400 → 400
  public.settings                            2 → 2
  storage.buckets                            1 → 1
  storage.objects                          120 → 120
  supabase_migrations.schema_migrations      2 → 2

  notes
    - the rows of supabase_functions.hooks are left out of the backup by design, as platform data
      Supabase writes for itself (its own restore guide leaves out the Storage vector tables); their
      definitions are checked
    - 1 Supabase platform schema(s) left out of the backup, as `supabase db dump` does: graphql

An email when a weekly restore test fails or does not run is not built.
It would email the people you name when a test fails, when the backup shrinks, or when no test has
run for eight days, from outside GitHub's scheduler. Today GitHub emails one person when a scheduled
run fails, and nobody when it never runs. agentcheck's free hourly watch covers MCP servers only. If
you would pay for this one, say so with one click:
https://agentwares-agentcheck.vercel.app/restoreproof?ref=restoreproof#watch

By provider

ProviderWhat it keepsIts own GitHub Actions recipe
SupabaseFree: none. Pro: 7 days of daily backups; Team 14; Enterprise 30. Never the files in Storage.Dumps daily and commits the dump to your repository. No restore.
NeonA restore window: 6 hours on Free, up to 7 days on Launch, up to 30 on Scale.Dumps nightly to S3. No restore.

Questions people search

An email when a weekly restore test fails or does not run is not built.

It would email the people you name when a weekly test fails, when the backup shrinks, or when no test has run for eight days, from outside GitHub's scheduler. Today GitHub emails one person when a scheduled run fails, and nobody when it never runs: it can drop a scheduled run under load, and it turns off a public repository's schedules after 60 days without activity. agentcheck's free hourly watch covers MCP servers only today. If you would pay for this one, say so with one click. The click is counted; nothing else is sent or stored.

Other ways to keep a tested backup

As their own pages state them, read 9 October 2026. restoreproof differs in where it runs: in your own GitHub Actions, so the data goes nowhere but your artifacts and your bucket.

ProductPriceTests the restoreWhere the data goes
Supabase point-in-time recoveryabout $100 a month for 7 days, on a paid plannoSupabase
BackupDrillfree for 1 project weekly; $19 a month for 5yesyour bucket, through its workers; Supabase only
ReviveDBfree for 1 project weekly; €9 a month for 3yesits own storage; Supabase only
SimpleBackupsfree for 1 job; then $49, $99, $299 a monthno, restores are by handyour storage, through its servers
Databasusfree, self-hostedyesa server you run

Sources

restoreproof is a free tool from agentcheck, which runs scheduled checks of your endpoints and alerts you when an answer changes. Facts come from each provider's own pages, read on the day shown.