# What is database branching?

Database branching makes a quick, isolated copy of a database for one pull request or agent task, so its writes and migrations never touch shared data.

Last updated September 29, 2026, 8 min read

## Learning objectives

After reading this article you will be able to:

-   Define database branching
-   Explain how a branch copies data quickly
-   Compare branching with test data seeding

## Related content

-   [What is a sandbox for AI agents?](https://specstory.com/learning/environments/ai-sandbox)
-   [What is test data seeding?](https://specstory.com/learning/environments/test-data-seeding)
-   [What is an ephemeral environment?](https://specstory.com/learning/environments/ephemeral-environments)
-   [What is test isolation (hermetic tests)?](https://specstory.com/learning/environments/test-isolation)
-   [What is a staging environment?](https://specstory.com/learning/environments/staging-environment)

## What is database branching?

Database branching is a practice that gives each Git branch, [pull request](https://specstory.com/learning/code-review/pull-request), or agent task its own quick copy of a database. Some tools call the copy a clone or a fork. The commands and tests of a [coding agent](https://specstory.com/learning/ai-coding/coding-agent) write to the branch, so shared data stays as it was.

A branch is a disposable database made from a parent database, e.g. a template database that a seed script fills. Teams often create a branch when a pull request opens and delete it when the pull request closes. An [ephemeral environment](https://specstory.com/learning/environments/ephemeral-environments) built for that pull request often gets its database this way. Some hosted services branch only the schema by default, so each branch starts with empty tables that a seed script fills.

Branching complements a [sandbox](https://specstory.com/learning/environments/ai-sandbox). The sandbox limits which files and hosts an agent's commands can reach, and the branch gives those commands a database they are allowed to change.

A branch runs the same database engine and version as its parent. An in-memory substitute, e.g. SQLite in the tests of a PostgreSQL app, often runs faster but is a different engine. It can accept queries that production rejects, so [integration testing](https://specstory.com/learning/testing/integration-testing) usually runs against the real engine.

## How does database branching work?

A branch usually goes through five steps:

1.  A parent database holds the starting state, e.g. records loaded by [test data seeding](https://specstory.com/learning/environments/test-data-seeding).
2.  When a task or pull request starts, a script or pipeline creates a branch from the parent's current state.
3.  The script passes the branch's connection string to the task, e.g. in a `DATABASE_URL` environment variable.
4.  The task's commands, e.g. a migration, change only the branch.
5.  When the task ends or the pull request closes, the script deletes the branch.

Step 2 is quick when the branch shares storage with its parent. The branch starts with no data of its own and reads the parent's blocks as they were when the branch was made. When it changes a block, it stores its own copy, a method called copy-on-write.

An OpenZFS clone is a writable copy of a snapshot, and the [OpenZFS manual](https://openzfs.github.io/openzfs-docs/man/master/7/zfsconcepts.7.html) says creating one is nearly instantaneous. A database server can then run on the cloned data files. Some hosted database services build the same method into their storage.

Diagram: How a branch shares blocks with its parent

Creating the branch copies no blocks. The branch stores only the blocks it changes, and the parent stays as it was.

PostgreSQL can also make a branch on its own. Its [template databases](https://www.postgresql.org/docs/current/manage-ag-templatedbs.html) page explains that `CREATE DATABASE` works by copying an existing database, and any database in the cluster can be the template. The default strategy, `WAL_LOG`, copies the template block by block, so the copy takes longer as the template grows. Since PostgreSQL 18, the [`FILE_COPY` strategy](https://www.postgresql.org/docs/current/sql-createdatabase.html) with the `file_copy_method` setting at `CLONE` lets the kernel share disk blocks on file systems that support it.

## What is an example of database branching?

Here is an illustrative example. Acme Co. sells furniture online. Its developers share one PostgreSQL development database on `db.example.com`, where a seed script also fills a template database, `acme_base`, that nobody connects to. A developer asks a coding agent to "Let customers edit their delivery address during checkout." Before the agent starts, a setup script makes a branch for the task:

```bash
createdb --host=db.example.com --template=acme_base acme_task_318
export DATABASE_URL=postgres://db.example.com:5432/acme_task_318
```

The task then runs in these steps:

1.  The branch starts with the same 20 test customers and carts as `acme_base`.
2.  The agent writes a migration that adds a `delivery_address` column to `orders` and runs it on the branch.
3.  The agent's checkout test fails. The address saved, but the cart emptied.
4.  To start the retry from a clean state, the agent runs a reset script that truncates `cart_items`. It empties the carts in the branch only.
5.  The setup script remakes the branch from `acme_base`, so the seeded carts are back, and the agent reruns its migration.
6.  The agent fixes the checkout code, and the test passes with 2 items still in the cart.
7.  When the agent's pull request merges, the pipeline drops `acme_task_318`.

On the shared database, step 4 would have emptied the carts that other developers' tests read. This example is simplified. A real setup would also give each task its own copy of other stored state, e.g. a cache.

## What changes when a coding agent writes the code?

A coding agent runs database commands as ordinary steps in its loop, e.g. a reset script while it debugs a failing test. Each command runs against whatever database the agent's connection string names. When that string points at a shared or production database, one reset deletes rows that people and other agents rely on.

[Parallel coding agents](https://specstory.com/learning/ai-coding/parallel-coding-agents) add a second problem. A Git worktree gives each agent its own files but not its own database. One agent's migration can then rename a column that another agent's code still reads, which breaks [test isolation](https://specstory.com/learning/environments/test-isolation). A [background coding agent](https://specstory.com/learning/ai-coding/background-coding-agents) works while nobody watches, so a destructive command may stay unnoticed until review.

A practical adjustment is to create one branch for each agent task and give the agent only that branch's connection string. Following [least privilege](https://specstory.com/learning/environments/least-privilege-for-ai-agents), keep credentials for shared and production databases out of the agent's environment. A bad reset or migration then means remaking one branch, not restoring shared data.

## What are the limits of database branching?

Branching has these limits:

-   **A branch is a copy from one moment.** Later changes to the parent do not reach the branch, and the branch's changes do not flow back. The parent keeps its old schema until the merged code's migrations run against it.
-   **Writes take real space.** Shared blocks save space only while unchanged. A migration that rewrites a large table stores a full copy of it in the branch.
-   **PostgreSQL copies need a quiet template.** The copy fails if another session is connected to the template when it starts, so teams keep a template that nobody uses.
-   **A production branch holds real data.** An agent that uses it can read real customer records. Masking personal data in the parent first, as many teams do for a [staging environment](https://specstory.com/learning/environments/staging-environment), keeps them out.
-   **Clones hold on to their parent.** In OpenZFS, a clone's snapshot cannot be destroyed while the clone exists, so forgotten branches keep old data on disk.

A branch also checks nothing. Tests on it show only that the code handled the records it held.

## How is database branching different from seeding?

Seeding builds a known state by running a script that inserts records into an empty database. Branching copies a database that already holds its schema and rows, without inserting them again. A seed takes longer as it inserts more records. A branch that shares blocks with its parent copies no rows when it is made, so it stays quick as the database grows.

The two often work together. A team seeds one parent database from scripts in the repository and branches it for each task, as Acme did. A [test fixture](https://specstory.com/learning/testing/test-fixture) works at a smaller scale. It sets up and removes the rows that one test needs, inside whichever database the run uses.

## FAQs

### Can a database branch start from production data?

A database branch can start from production data, and the branch then holds real customer records that an agent can read. Masking personal data in the parent before branching keeps those records out of each branch made from it.

### How do schema migrations run on a branch?

Schema migrations run on a branch through the project's usual migration tool, pointed at the branch's connection string. The migration changes only the branch, so the parent keeps its old schema until the merged code's migrations run against it.

### When should a database branch be deleted?

A database branch should be deleted when the task or pull request it served ends, e.g. when the pull request merges or closes. Deleting it frees the space its writes used. In OpenZFS, it also lets the parent's snapshot be destroyed.

### Can an in-memory database replace a branch in tests?

An in-memory database often runs faster than a branch, but it can replace one only where the engine does not affect the result. It is usually a different engine from production, e.g. SQLite in place of PostgreSQL, and it can accept queries that the real engine rejects.

---

Source: [What is database branching? | AI coding agents | SpecStory](https://specstory.com/learning/environments/database-branching)
