Skip to content

Martello-Systems/snapdb

Repository files navigation

snapdb

License: Apache 2.0 Built by Martello Systems

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 second

No 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.


Why

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.

Install

npm install -g github:Martello-Systems/snapdb
# or run without installing:
npx github:Martello-Systems/snapdb --help

Requires Node 18+ and a local PostgreSQL (the createdb/psql server, not a client).

Connection

snapdb finds your database the same way your app does:

  1. --url postgres://... flag
  2. DATABASE_URL environment variable
  3. DATABASE_URL in a .env file in the current directory
export DATABASE_URL=postgres://me@localhost:5432/myapp_dev
snapdb info

Commands

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).

diff: what changed since a checkpoint

$ 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    changed

Row 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.

prune: keep your snapshot dir tidy

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 deleting

At least one of --older-than / --keep is required (no rule = no-op guard). Durations accept s, m, h, d, w.

Example

$ 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-seed

How it works

Snapshots 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.

What restore preserves

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 had REVOKE CONNECT ... FROM PUBLIC, the restored database also denies PUBLIC (snapdb strips the fresh clone's default PUBLIC CONNECT/TEMPORARY before 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 from pg_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.

Limitations

  • Postgres only, by design, not by accident. snapdb's whole value is the instant, file-level CREATE DATABASE ... WITH TEMPLATE copy. 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 rm to reclaim it.
  • diff row counts are planner estimates (reltuples); ANALYZE for exactness.

Roadmap

  • branch / checkout ergonomics (git-style branch ↔ snapshot mapping).
  • Optional exact COUNT(*) mode for diff on small databases.
  • A non-template logical-copy fallback for cross-server copies.
  • (Not planned: MySQL, see Limitations for why.)

Demo

Demo GIF placeholder: save → break → restore in ~1 second. Drop a recording in docs/demo.gif and reference it here before launch.

Development

npm install
npm run lint    # eslint (flat config)
npm test        # node:test, pure unit tests + integration tests vs real Postgres

Integration 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 test

To 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.

License

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.


Built by Martello Systems

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.

About

Instant Postgres dev-database snapshots — save / restore / diff / prune in ~1s via CREATE DATABASE WITH TEMPLATE. By Martello Systems.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors