Database Branch Management¶
When to use: Before any schema change, migration, destructive query, or risky data work against a managed Postgres database — validate it on a short-lived branch first, never directly on staging or production.
Instructions¶
Modern managed Postgres (Neon, Databricks Lakebase) supports copy-on-write
branches: a near-instant, isolated copy of the database you can mutate freely
and throw away. Treat every schema change, migration, or destructive query as
something that must be proven on a branch before it reaches staging or
production.
This skill covers two providers. Pick the section that matches your stack:
- Neon —
neonctlCLI workflow (concrete, end-to-end below). - Lakebase (Databricks managed Postgres) — equivalent workflow via the Databricks CLI/API.
The governance principles are shared and provider-agnostic; see database-change-control for the schema-first change-control rules this workflow operates inside.
Core rules (both providers)¶
- Never run a migration or destructive SQL (
DROP,TRUNCATE,ALTER ... DROP COLUMN) againststaging/productionbefore validating it on a branch. - Always set an expiry/TTL on temporary branches (default 48 hours) so exploratory copies clean themselves up. Skip expiry only for long-lived branches tied to a specific feature branch in CI.
- Name branches for what they do:
dev/<name>-explore,schema/add-orders-table,migration/0005-add-indexes. - Production migrations are CI's job. Validate on a branch locally, commit the migration files, open a PR. The deploy pipeline applies migrations to production on merge — you do not run them against production by hand.
Neon workflow¶
Prerequisites¶
neonctl --version # check the CLI is installed
npm install -g neonctl # install if missing — https://neon.tech/docs/reference/cli-install
neonctl auth # authenticate (first time only)
neonctl projects list # find your project id
neonctl set-context --project-id <project-id> # avoid --project-id on every call
1. Create a branch (with a 48-hour TTL)¶
# Linux/GNU (WSL2)
neonctl branches create --name <name> --expires-at "$(date -u -d '+48 hours' +%Y-%m-%dT%H:%M:%SZ)"
# macOS/BSD
neonctl branches create --name <name> --expires-at "$(date -u -v+48H +%Y-%m-%dT%H:%M:%SZ)"
For a longer-lived branch tied to a GitHub feature branch, omit --expires-at
and delete it manually when the branch merges:
2. Get the branch connection string¶
Returns a postgresql://... URI. Use it in place of DATABASE_URL for every
subsequent operation against the branch.
3. Apply changes on the branch¶
Prefer your project's branch-safe wrapper scripts if it has them — they
validate the target is a temporary branch (not main/staging/production)
and wire up the connection string. For example, a Drizzle-based project might
expose:
npm run db:branch:migrate -- <branch-name> # run a generated migration
npm run db:branch:push -- <branch-name> # push schema for fast iteration
If your project blocks raw migration tools (e.g. drizzle-kit push/migrate)
via deny rules, the wrapper scripts are the only sanctioned path — use them.
4. Validate¶
DATABASE_URL="<branch-connection-string>" npm test
neonctl branches schema-diff <branch-name> --compared-to main # optional
Do not proceed until validation passes.
5. Apply to the real database¶
Commit the migration files, push to a feature branch, open a PR. CI validates on a fresh temp branch and applies to production on merge. Do not run migrations against staging/production locally.
6. Clean up¶
If the branch had --expires-at, cleanup is automatic and this step is optional.
Neon quick reference¶
| Task | Command |
|---|---|
| List branches | neonctl branches list |
| Create branch | neonctl branches create --name <name> |
| Get connection string | neonctl connection-string <branch-name> |
| Compare schemas | neonctl branches schema-diff <branch> --compared-to main |
| Reset branch to parent | neonctl branches reset <branch-name> --parent |
| Delete branch | neonctl branches delete <branch-name> |
Lakebase workflow (Databricks managed Postgres)¶
Lakebase exposes the same branching idea through Databricks database
instances: you create a child instance from a parent at a point in time,
work against it, then delete it. The Databricks CLI surface is evolving — run
databricks database --help and confirm exact flags against current docs
(Lakebase docs) before scripting.
The workflow mirrors Neon:
- Authenticate with the Databricks CLI (
databricks auth login) and select the target workspace profile. - Create a child branch from the parent instance at the current (or a point-in-time) state. A child instance is an isolated copy-on-write Postgres you can mutate freely.
- Get the branch connection details for the child instance and use them in
place of your production
DATABASE_URL. - Apply and validate schema changes / migrations against the child, then run the test suite against it — same gate as Neon step 4.
- Promote via CI, not by hand: commit migration files and let the deploy pipeline apply them to the production instance.
- Delete the child instance when done; set a TTL/expiry where the API supports it so abandoned branches don't linger and accrue cost.
Because Lakebase child instances consume compute/storage, deleting them promptly matters more than with Neon — treat cleanup as mandatory, not optional.
References¶
- Source skill (Neon-only):
vortexnav-platform/.agents/skills/neon-database/SKILL.md - Neon CLI branching guide
- Neon CLI reference — branches
- Databricks Lakebase (OLTP) docs
- database-change-control — the schema-first governance rules this workflow operates inside.
Troubleshooting¶
| Symptom | Likely cause | Fix |
|---|---|---|
neonctl: command not found |
CLI not installed | npm install -g neonctl |
Commands prompt for --project-id every time |
Context not set | neonctl set-context --project-id <id> |
| Migration tool blocked / permission denied | Project deny rules forbid raw drizzle-kit etc. |
Use the project's branch-safe wrapper scripts instead |
| Branch still exists after you expected cleanup | No --expires-at was set |
Delete manually (neonctl branches delete <name>); set a TTL next time |
databricks database flags rejected |
Lakebase CLI surface drifted | Run databricks database --help and confirm against current docs before scripting |
| Unexpected Lakebase compute/storage cost | Child instances left running | Delete child instances promptly; set a TTL where supported |