The idea
A service that keeps your product's public demo honest. You point it at your Postgres database and declare one tenant as the golden demo. It snapshots that tenant's rows, and every visitor who clicks "try the demo" gets their own clone of the snapshot, loaded as a new tenant in the same database and reachable through a session cookie. An hour later the clone is deleted. Nobody is editing anyone else's demo, and the golden copy is only ever written to by you.
Why build this
Live demos convert better than screenshots and better than a signup wall, which is exactly why every indie SaaS eventually tries one and then quietly takes it down. The failure is always the same: someone deletes the sample project, someone renames every column to test the UI, someone pastes profanity into the shared board, and the next prospect sees that instead of your product. So the demo gets hidden behind an email form, and the best thing on the landing page is gone.
The two existing fixes both cost more than they should. A nightly pg_restore cron is easy to write but leaves the mess visible for the rest of the day, and it restores the whole database, so it only works if the demo lives in its own instance. Per-visitor full-stack sandboxes — a container or a preview environment per click — are clean but expensive, slow to boot, and overkill when nothing about the app binary is changing.
Row-level tenant cloning sits in between, and it is now cheap to build because almost every B2B app shipped in the last decade is multi-tenant with a tenant_id (or org_id, or workspace_id) on every table. Once that column exists, cloning a tenant is a schema walk and a few COPY statements, not an infrastructure problem. The hard parts are foreign keys and discipline, both of which are solvable once and reusable everywhere.
Stack sketch
- Schema walk: read
information_schema.columnsandpg_constraintto find every table carrying the tenant column and the foreign-key graph between them.pg_dumpcannot filter rows, so this is the part you have to write. - Clone: topologically sort tables by FK dependency, then
COPY (SELECT ... WHERE tenant_id = $1) TO STDOUTinto compressed files. Restore in one transaction in reverse-free order, regenerating UUID primary keys and rewriting the FK columns through an in-memory id map. For integer sequences, offset instead of remapping. - Snapshot store: the dump files in S3-compatible object storage (MinIO works), with the schema fingerprint recorded alongside so a clone refuses to run against a migrated database.
- Session mapping: Redis, holding cookie token to cloned tenant id with a TTL. The TTL is also the deletion trigger.
- Request path: a Traefik ForwardAuth service or a small middleware in the app that reads the demo cookie and injects the tenant id, so the app itself needs no demo-specific code.
- Runtime: Go for the cloner — it is mostly
pgx, goroutines, and careful ordering. - Smoke test: Playwright on a cron, clicking through a fresh clone after every snapshot update, because a demo that 500s is worse than a polluted one.
Scope for v1
- Postgres only, one schema, one tenant column name declared in a config file.
- Two commands:
demo snapshot(freeze the golden tenant) anddemo clone(hand back a tenant id), plus one HTTP endpoint wrapping the second. - A table allowlist in config. Anything not listed is never copied, and the tool fails loudly on an unlisted table that holds the tenant column.
- A cleanup worker that deletes expired clones in FK-safe order.
- Deliberately out: blob and file storage cloning, multiple databases, MySQL, external side effects (email, Stripe, webhooks), resuming a demo the next day, and any UI beyond a status page.
Where it could go
The obvious next step is external state. A demo that sends email or creates a subscription needs sandbox credentials and a mail catcher bound to the clone, so the visitor sees their own welcome email in an embedded inbox rather than nothing happening. That turns the demo from a read-only tour into something that exercises the whole funnel.
The bigger path is reuse of the cloner itself. The same tenant-scoped extraction, with a redaction pass over the COPY stream, gives a support engineer a single customer's data in a local dev database — which is how most teams reproduce bugs today, badly, by hand-writing seed scripts. Pointed at CI instead, it gives every preview branch a realistic dataset without restoring a production snapshot. The demo is the wedge; tenant-level data movement is the product.
Watch out for
Referential integrity and PII, in that order of frequency and reverse order of consequence. A clone that misses a shared lookup table, or follows a cross-tenant foreign key one hop too far, produces a demo that half-works in ways that are hard to debug and embarrassing in front of a prospect — so make the allowlist explicit and fail the snapshot when the schema changes underneath it. The serious risk is that golden tenants tend to start life as a real customer's account; if yours did, you are about to copy their data onto a public URL, and no amount of TTL cleanup undoes that.