Skip to content
Discord Get Started

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.

  • A DB9 account with the CLI installed (see Quick Start)
  • An existing database to branch from
Terminal
db9 create --name production

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.

  1. Create a Branch

    Terminal
    db9 branch create production --name feature-auth

    The command returns immediately with the branch details:

    Output
    Branch 'feature-auth' created from database production.
    ID: <branch-id>
    Name: feature-auth
    State: CLONING
    Region: us-west-2
    Admin User: admin

    Admin User is the role name only. For a direct PostgreSQL connection, use the username from db9 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-auth

    Or in a script:

    Terminal
    while true; do
    state=$(db9 db status feature-auth --json | jq -r .state)
    if [ "$state" = "ACTIVE" ]; then
    echo "Branch is ready"
    break
    elif [ "$state" = "CREATE_FAILED" ]; then
    echo "Branch creation failed"
    db9 db status feature-auth --json | jq .state_reason
    exit 1
    fi
    sleep 2
    done

    Show 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-secrets is 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 — use db9 db connect instead for short-lived credentials.

  2. Connect to a Branch

    A branch is a regular DB9 database. Connect with any PostgreSQL tool:

    Terminal
    db9 db connect feature-auth

    Or get the connection string:

    Terminal
    db9 db connect feature-auth --output quiet

    Then use it with psql, your ORM, or any PostgreSQL driver.

  3. List Branches

    Terminal
    db9 branch list production

    Shows all branches of a database with their state and creation time. Supports --output json and --output csv for programmatic use.

  4. Delete a Branch

    Terminal
    db9 branch delete feature-auth -y

    Without -y the command asks for confirmation. When nothing answers it — in CI, or any script whose stdin is closed or empty — it prints Cancelled. and exits 0 without deleting the branch, so always pass -y in scripts. Deletion is permanent — there is no undo or trash. Deleting a branch does not affect the parent database.

  5. 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 ready
    let 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 credentials
    const 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 done
    await client.databases.delete(branch.id);

Create a branch per pull request for integration testing:

Terminal
BRANCH_NAME="pr-${PR_NUMBER}"
# Create branch from production
db9 branch create production --name "$BRANCH_NAME"
# Wait for it
while [ "$(db9 db status "$BRANCH_NAME" --json | jq -r .state)" != "ACTIVE" ]; do
sleep 2
done
# Run migrations and tests against the branch
db9 db sql "$BRANCH_NAME" -f migrations/latest.sql
npm 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" -y

Test a migration in an isolated branch before applying it:

Terminal
# Branch production
db9 branch create production --name migration-test
# Wait for ready
# ...
# Apply migration
db9 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 production
db9 db sql production -f migrations/0042_add_index.sql
# Clean up test branch
db9 branch delete migration-test -y

Give each agent task its own database branch so parallel agents don’t interfere with each other:

Terminal
TASK_ID="task-$(uuidgen | head -c 8)"
db9 branch create shared-db --name "$TASK_ID"
# ... wait for ACTIVE ...
# Agent works against isolated branch
db9 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 completes
db9 branch delete "$TASK_ID" -y
  • Asynchronous creation — branches are not immediately ready. Always poll for ACTIVE state 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 claim to 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.