Branching Workflows
DB9 can create a branch of any database — an independent copy with its own credentials, connection string, and data. Branches are useful for preview environments, isolated testing, safe schema migrations, and data recovery.
This guide shows how to create, use, and manage branches through the CLI and SDK.
Prerequisites
Section titled “Prerequisites”- A DB9 account with the CLI installed (see Quick Start)
- An existing database to branch from
db9 create --name productionHow Branching Works
Section titled “How Branching Works”When you create a branch, DB9 clones schema, data and fs9 files from the source database into a new, independent database. Serverless functions and their secrets are not copied — a branch starts with none. The branch gets its own:
- TiKV keyspace (isolated storage)
- Admin credentials
- Connection string
- Lifecycle (create, delete independently of the parent)
Changes to the branch do not affect the parent, and changes to the parent do not propagate to the branch.
Branch creation is asynchronous — the command returns immediately with status CLONING, and you poll until the branch reaches ACTIVE.
-
Create a Branch
Terminal db9 branch create production --name feature-authThe command returns immediately with the branch details:
Output Branch 'feature-auth' created from database production.ID: <branch-id>Name: feature-authState: CLONINGRegion: us-west-2Admin User: adminAdmin Useris the role name only. For a direct PostgreSQL connection, use the username fromdb9 db connect feature-auth, which includes the branch tenant ID prefix.Wait for the branch to be ready
Poll the branch status until it reaches
ACTIVE:Terminal db9 db status feature-authOr in a script:
Terminal while true; dostate=$(db9 db status feature-auth --json | jq -r .state)if [ "$state" = "ACTIVE" ]; thenecho "Branch is ready"breakelif [ "$state" = "CREATE_FAILED" ]; thenecho "Branch creation failed"db9 db status feature-auth --json | jq .state_reasonexit 1fisleep 2doneShow credentials on creation
Terminal db9 branch create production --name staging --show-secrets# Or individually:db9 branch create production --name staging-2 --show-password --show-connection-string--show-secretsis shorthand for--show-password --show-connection-string. This prints the admin password (Admin Pass:) and the connection string in the output; the connection string itself carries no password. Avoid this in CI logs — usedb9 db connectinstead for short-lived credentials. -
Connect to a Branch
A branch is a regular DB9 database. Connect with any PostgreSQL tool:
Terminal db9 db connect feature-authOr get the connection string:
Terminal db9 db connect feature-auth --output quietThen use it with
psql, your ORM, or any PostgreSQL driver. -
List Branches
Terminal db9 branch list productionShows all branches of a database with their state and creation time. Supports
--output jsonand--output csvfor programmatic use. -
Delete a Branch
Terminal db9 branch delete feature-auth -yWithout
-ythe command asks for confirmation. When nothing answers it — in CI, or any script whose stdin is closed or empty — it printsCancelled.and exits0without deleting the branch, so always pass-yin scripts. Deletion is permanent — there is no undo or trash. Deleting a branch does not affect the parent database. -
SDK Usage
Use the TypeScript SDK for programmatic branch management:
TypeScript import { createDb9Client } from 'get-db9';const client = createDb9Client();// Create a branch (first argument is the parent database ID)const branch = await client.databases.branch('production-db-id', {name: 'feature-auth'});console.log(branch.id, branch.state); // "CLONING"// Poll until readylet status;do {status = await client.databases.get(branch.id);if (status.state === 'CREATE_FAILED') throw new Error(`Branch ${branch.id} failed to clone`);await new Promise(r => setTimeout(r, 2000));} while (status.state !== 'ACTIVE');// Use the branch. Its connection_string carries no password, so build a URL// from the branch's stored credentialsconst creds = await client.databases.credentials(branch.id);const url = new URL(creds.connection_string);url.password = creds.admin_password;url.searchParams.set('sslmode', 'verify-full');console.log(url.toString());// Delete when doneawait client.databases.delete(branch.id);
Practical Workflows
Section titled “Practical Workflows”Preview environments in CI
Section titled “Preview environments in CI”Create a branch per pull request for integration testing:
BRANCH_NAME="pr-${PR_NUMBER}"
# Create branch from productiondb9 branch create production --name "$BRANCH_NAME"
# Wait for itwhile [ "$(db9 db status "$BRANCH_NAME" --json | jq -r .state)" != "ACTIVE" ]; do sleep 2done
# Run migrations and tests against the branchdb9 db sql "$BRANCH_NAME" -f migrations/latest.sqlnpm test -- --database-url="$(db9 db connect "$BRANCH_NAME" --output quiet)"
# Clean up (-y: without it, a non-interactive delete is cancelled and the branch stays)db9 branch delete "$BRANCH_NAME" -ySafe schema migration testing
Section titled “Safe schema migration testing”Test a migration in an isolated branch before applying it:
# Branch productiondb9 branch create production --name migration-test
# Wait for ready# ...
# Apply migrationdb9 db sql migration-test -f migrations/0042_add_index.sql
# Validate (EXPLAIN returns no rows over db9 db sql — run it through psql)db9 db sql migration-test -q "SELECT count(*) FROM users"psql "$(db9 db connect migration-test --output quiet)" \ -c "EXPLAIN SELECT * FROM users WHERE email = 'test@example.com'"
# If satisfied, apply to productiondb9 db sql production -f migrations/0042_add_index.sql
# Clean up test branchdb9 branch delete migration-test -yTask isolation for agents
Section titled “Task isolation for agents”Give each agent task its own database branch so parallel agents don’t interfere with each other:
TASK_ID="task-$(uuidgen | head -c 8)"
db9 branch create shared-db --name "$TASK_ID"# ... wait for ACTIVE ...
# Agent works against isolated branchdb9 db sql "$TASK_ID" -q "CREATE TABLE scratch (id SERIAL, data JSONB)"db9 db sql "$TASK_ID" -q "INSERT INTO scratch (data) VALUES ('{\"result\": \"done\"}')"
# Clean up when task completesdb9 branch delete "$TASK_ID" -yLimits and Caveats
Section titled “Limits and Caveats”- Asynchronous creation — branches are not immediately ready. Always poll for
ACTIVEstate before connecting. - Full data copy — branch creation clones schema, data and fs9 files from the parent.
- No functions or secrets — serverless functions and function secrets are not copied; deploy them to the branch again if you need them.
- Max 2 concurrent branch creations — attempting a third returns a 429 error. Wait for in-progress branches to finish.
- Branches count toward database quota — anonymous accounts are limited to 5 databases total (including branches). Run
db9 claimto remove the limit. - No automatic merge — there is no built-in way to merge branch changes back to the parent. Export and re-apply manually.
- Deletion is permanent — deleted branches cannot be recovered.
- Branch naming — names must be unique within your account and cannot be empty.
Next Pages
Section titled “Next Pages”- CI Ephemeral Databases — disposable databases and branches in CI pipelines
- CLI Reference —
db9 branchcommand details and global flags - TypeScript SDK —
databases.branch()and programmatic management - Scheduled Jobs with pg_cron — automate branch cleanup or scheduled workflows
- Agent Workflows — using branches in agent pipelines
- Platform: Provisioning — database creation and lifecycle
- Recovery and Branch Lifecycle — branch states, automatic recovery, and backup expectations