Datagraft is git checkout for a live PostgreSQL: fork the database you already have into an isolated server, optionally take ~N rows instead of the whole thing, and leave origin up. It is not a new engine, not a cluster, and not merge. A branch is a second Postgres. Reset recreates it. There is no datagraft merge.
datagraft checkout postgres-v1 --sample 50 never written
│
origin ──────────────┤ :5432 1,000,000 rows
│
postgres-v1 ─────────┘ :5435 50 rows ← yours
│
ALTER TABLE, bad migrations, seed experiments
This is still an experiment. The pass condition is a real app booting on that fork. If that loop does not stick, merge and remotes are not worth building.
Why I built it
Code is branched. The database usually is not.
I kept hitting that on a particular app: EF Core, uuid primary keys, jsonb, quoted "Id" columns, dual FKs to users. Every “what if we add this column,” every migration, every seed experiment either ran against the shared Postgres or waited until staging. QA and other developers ate the blast radius, or nobody tried anything. The workaround was pg_dump / restore: slow, stale, still one copy. You cannot have postgres-v1 and broken-fk-experiment at the same time without two restores.
Hosted branching (Neon, Postgres.ai) isolates in the cloud. It is not a laptop CLI, and it does not make the data smaller. Git-like engines (Doltgres) solve merge by not being Postgres. The app’s extensions, SQL, and ORM are not the same product.
I wanted the gap in between: real Postgres, installable, isolated, optionally fifty rows. That app’s schema was the first the experiment had to survive. Checkout in about five seconds. Isolation held — DDL on the fork never appeared on origin. App-boot SQL passed. The app process itself is still the next proof.
I did not want a Kubernetes operator, a CoW volume clone, or a fake merge. I wanted datagraft checkout the way I already git checkout. Reset is how you iterate. Merge waits until a real boot says otherwise.
Tech Stack
- Go — installable CLI (
datagraft), no cluster required - PostgreSQL — the engine is real Postgres; schema via
pg_dump --schema-only, data via a logical subset - Docker or local
pg_ctl— throwaway postmaster on a free port; Docker if the daemon answers in 3s, else Homebrew-style local Postgres - FK-safe sampler — topological copy, PK required, parents before children; dual FKs close the other side instead of AND-sampling to zero rows
Architecture Overview
Datagraft never writes origin. Isolation is a second postmaster, not a DELETE that “makes it smaller.”
init— records origin (read-only from here on). Hosts that look like RDS / Neon / Supabase are refused unless you pass--i-knowcheckout— starts a throwaway Postgres on a free port (never origin’s). Restores schema, then copies an FK-closed subset if--sample Nis set- Keep / seed —
--keep-pkand--include-sqlkeep the admin user, migrations table, and other rows the app needs to start reset— throws the instance away and rebuilds it. That is the iterate loop. Not merge- State —
.datagraft/state.jsonplus metrics;env/status/diff/drop/gcaround the same model
sequenceDiagram
autonumber
participant You
participant CLI as datagraft
participant Origin as origin Postgres
participant Branch as branch Postgres
You->>CLI: init with origin URL
CLI->>Origin: connect (read)
You->>CLI: checkout postgres-v1 sample 50
CLI->>Branch: start on a free port
CLI->>Origin: pg_dump schema-only
CLI->>Branch: restore schema
CLI->>Origin: read-only subset of rows
CLI->>Branch: copy subset and setval
CLI-->>You: branch URL on a new port
Note over Origin: still serving everyone else
An early design treated this as GitOps CRDs and a Kubernetes operator. That failed the install-and-run test. The interface is a CLI. GitOps, if it ever appears, wraps the same binary.
Key Features
- Named branches —
checkout postgres-v1is a second server, not a schema prefix on origin --sample N— logical copy of ~N rows plus FK parents; small forks are actually small--keep-pk/--include-sql— keep the rows a real app needs (admin,schema_migrations)- Reset, not merge — recreate the fork from parent with the same sample policy
- Origin stays up — other clients keep their URL; origin port is never stolen
- Safety rails — refuse prod-looking hosts unless
--i-know; origin credentials are not the printed branch URL - Doctor / status / env / diff — explain failed checkouts, remind you origin still has N rows, point the app at the fork
What this is not
Not Neon. Not Doltgres. Not a Kubernetes operator. Not CoW volume clones. Not merge, remotes, or GitOps. Those are deferred until reset plus a real app boot say the loop is worth keeping.
Try it
Needs Go 1.25+, pg_dump / psql, and either Docker or Homebrew postgresql@16.
go install github.com/machugram/datagraft/cmd/datagraft@latest
datagraft doctor
datagraft init --url postgres://localhost:5432/app
datagraft checkout postgres-v1 --sample 50
eval "$(datagraft env)"
datagraft reset postgres-v1