Instant save-points for your dev database. Snapshot, restore, and branch your local Postgres in milliseconds, built for the migration-and-branch-switch loop.
snapdb save before-migration # checkpoint your current dev DB
# ...run the scary migration, break everything...
snapdb restore before-migration # back to a clean state in ~1 secondNo more pg_dump / pg_restore dances, no more "drop and re-seed and wait." snapdb uses Postgres template databases to copy at the file level, so saving and restoring a multi-hundred-MB dev database takes about a second instead of minutes.
Every backend developer hits this loop:
- You're about to run a migration you're not sure about.
- You're switching branches and the schema is different.
- You just
DELETEd the wrong rows testing something.
The status quo is pg_dump before, pg_restore after: slow, clumsy, and so annoying that most people just don't, and then lose their seed data. snapdb makes a checkpoint a single fast command, so you actually use it.
npm install -g github:Martello-Systems/snapdb
# or run without installing:
npx github:Martello-Systems/snapdb --helpRequires Node 18+ and a local PostgreSQL (the createdb/psql server, not a client).
snapdb finds your database the same way your app does:
--url postgres://...flagDATABASE_URLenvironment variableDATABASE_URLin a.envfile in the current directory
export DATABASE_URL=postgres://me@localhost:5432/myapp_dev
snapdb info| Command | What it does |
|---|---|
snapdb save [name] |
Save a snapshot of the current database (default name latest). |
snapdb restore [name] |
Restore the database from a snapshot. Auto-backs-up current state first. |
snapdb diff [name] |
Show the per-table row-count delta of the current database vs a snapshot. |
snapdb list (ls) |
List snapshots for the current database. |
snapdb prune |
Remove snapshots by age (--older-than 7d) and/or keep-last-N (--keep 5). |
snapdb rm <name> |
Delete a snapshot. |
snapdb gc |
Drop orphaned snapdb_tmp_* staging databases left by a crashed restore. |
snapdb info |
Show the connection target and snapshot count. |
Useful flags: --force (overwrite on save), --no-backup (skip the safety backup on restore), --yes (terminate blocking connections without asking), --json (machine-readable output), -m "note" (annotate a snapshot), --dry-run (preview a prune without deleting).
$ snapdb diff clean-seed
› myapp_dev vs snapshot clean-seed: 1 table(s) added, 0 removed, 1 changed
TABLE SNAPSHOT CURRENT DELTA STATUS
events - ~0 new added
users ~120 ~138 +18 changedRow counts are Postgres planner estimates (pg_class.reltuples): fast and lock-free, ideal for a "what did my migration touch" glance. Run ANALYZE first if you need fresh numbers.
snapdb prune --older-than 7d # drop anything older than a week
snapdb prune --keep 5 # keep only the 5 most recent
snapdb prune --older-than 30d --keep 10 # both rules (union of what each removes)
snapdb prune --keep 5 --dry-run # preview without deletingAt least one of --older-than / --keep is required (no rule = no-op guard). Durations accept s, m, h, d, w.
$ snapdb save clean-seed -m "fresh seed data"
✓ Saved snapshot clean-seed (24MB) of myapp_dev
$ snapdb ls
NAME SIZE AGE NOTE
clean-seed 24MB 4s ago fresh seed data
$ # ...you run a destructive experiment...
$ snapdb restore clean-seed
› current state backed up as before-restore-20260623-071500
✓ Restored from snapshot clean-seedSnapshots are real Postgres databases created with CREATE DATABASE ... WITH TEMPLATE, which copies data files directly (no SQL replay). A small JSON index at ~/.snapdb/registry.json maps your snapshot names to those databases, scoped per host:port/database so projects never collide.
Restoring is staged so a failure never leaves you without a database: snapdb first clones the snapshot into a throwaway snapdb_tmp_* database, and only once that succeeds does it drop the live database and rename the clone into its place. If anything fails before the rename, your original database is untouched; if the final rename itself fails, snapdb tells you the exact ALTER DATABASE ... RENAME TO to finish the swap by hand. If a crash leaves a snapdb_tmp_* database orphaned (it's invisible to snapdb ls), the next restore sweeps idle ones automatically and snapdb gc reclaims them on demand. Note that staging means a restore transiently needs disk for a second copy of the database.
CREATE DATABASE ... WITH TEMPLATE copies everything inside the database (schemas, data, object-level owners and grants, extensions) and, from the template, the encoding, collation, and ctype (locale). It does not copy the pg_database row's own properties, so after the swap snapdb reapplies them from the live target database — making the restored database an exact match, not merely a permissive superset:
- Owner (
ALTER DATABASE ... OWNER TO). - Connection limit (
ALTER DATABASE ... CONNECTION LIMIT). - Database-level privileges, faithfully — including non-default
REVOKEs. If the source hadREVOKE CONNECT ... FROM PUBLIC, the restored database also deniesPUBLIC(snapdb strips the fresh clone's defaultPUBLICCONNECT/TEMPORARYbefore replaying the captured grants, so it never silently widens access). - Per-database and per-role configuration parameters set via
ALTER DATABASE ... SET/ALTER ROLE ... IN DATABASE ... SET(captured frompg_db_role_setting).
What it does not manage: tablespace placement (the clone follows Postgres's default for a template copy) and the locale/encoding above are inherited from the template rather than re-derived. All reapplication is best-effort: anything the connecting role lacks privilege to reset (e.g. it isn't a member of the original owner role) is reported as a warning, not a failure — the restored data is already safely live by that point.
Because cloning a template requires no other sessions on it, snapdb will offer to terminate open connections (e.g. your running dev server). Pass --yes to do it automatically, or it asks first when run interactively.
- Postgres only, by design, not by accident. snapdb's whole value is the
instant, file-level
CREATE DATABASE ... WITH TEMPLATEcopy. MySQL has no equivalent, so supporting it would mean a fundamentally different logical-copy (mysqldump-style) engine that's slower and dilutes the "~1 second checkpoint" promise. We'd rather do one thing well. Pass a non-Postgres URL and snapdb fails fast with a clear message rather than half-working. - Local-dev tool. This is for fast disposable dev checkpoints, not backups or
disaster recovery. Use
pg_dump/ your provider's backups for that. - Cross-role connections. Terminating sessions owned by another Postgres
role needs
pg_signal_backend(or superuser). Terminating your own always works. If snapdb hits this, it tells you exactly what to grant:GRANT pg_signal_backend TO <your_role>; - Disk. Each snapshot consumes disk equal to your database size. Use
snapdb prune/snapdb rmto reclaim it. diffrow counts are planner estimates (reltuples);ANALYZEfor exactness.
branch/ checkout ergonomics (git-style branch ↔ snapshot mapping).- Optional exact
COUNT(*)mode fordiffon small databases. - A non-template logical-copy fallback for cross-server copies.
- (Not planned: MySQL, see Limitations for why.)
Demo GIF placeholder: save → break → restore in ~1 second. Drop a recording in
docs/demo.gifand reference it here before launch.
npm install
npm run lint # eslint (flat config)
npm test # node:test, pure unit tests + integration tests vs real PostgresIntegration tests need a local Postgres role with CREATEDB. Export its
credentials (never commit them) and run the suite:
SNAPDB_TEST_USER=youruser SNAPDB_TEST_PASSWORD=… npm testTo also exercise the cross-role connection-termination error path, provide a
second non-superuser role that lacks pg_signal_backend via
SNAPDB_TEST_OTHER_USER / SNAPDB_TEST_OTHER_PASSWORD; otherwise that test is
skipped.
Apache-2.0 © 2026 Martello Systems. See LICENSE.
Built by Martello Systems. We build AI-assisted software and ship the tools we use. See what else we make.
snapdb is part of the open-source toolkit from Martello Systems. We ship AI-built software, spec to delivery in days. If this saved you time, come see what we do.
Licensed under the Apache License 2.0.