Database Branching: A Developer's Guide to Git-Style Workflows
Git made isolated development a baseline for software teams. Each developer can create a branch, work independently, and merge changes when they're ready.
Database branching brings this same isolation to the database. It lets developers, continuous integration (CI) jobs, and AI agents create isolated database environments from a shared database state and make changes without affecting the parent database or each other.
That means less time waiting for shared environments, fewer test failures caused by other people's changes, and faster feedback on schema migrations. When something goes wrong, you can throw away the branch instead of repairing or restoring a shared database.
TL;DR
- Database branching creates isolated environments without full database copies. Copy-on-write makes this possible by sharing unchanged data and storing only what changes.
- Database branching is increasingly important for AI agents. It gives agents isolated environments to test changes and experiment without putting the parent database at risk.
- Operating database branches safely requires clear controls. Protect parent branches, restrict access to sensitive data, automate cleanup, and keep disposable environments reproducible.
What Is Database Branching?
Database branching gives you an isolated database environment based on another database's state at a specific point in time. The branch starts with the parent database's schema and data, but changes you make to the branch do not affect the parent or any sibling branches.
If you're familiar with Git, the basic idea should feel familiar. A code branch gives you a private line of development from a known commit. A database branch gives you an isolated database environment from a known database state.
One important difference is that you usually don't merge changes from a database branch back into the parent database. Instead, migration files remain the durable source of truth. You can test a migration on your branch, make sure it works against realistic data, and then let your deployment pipeline apply that same migration to the target database. In this way, database branching makes long-standing practices such as evolutionary database design, database-per-developer environments, and version-controlled migrations practical even when you're working with production-scale data.

As shown above, a parent database provides the known schema and data for multiple isolated branches. You can use a developer branch to modify and test changes, a pull request branch to run migrations and CI, or an agent branch to explore and evaluate changes. When the work is done, each branch can be reset, deleted, or pruned without affecting the parent database or the other branches. Database branching makes this level of isolation possible through copy-on-write.
How Copy-on-Write Database Branching Works
Copy-on-write (CoW) makes database branching practical by avoiding an upfront full copy of the database. When you create a branch, it initially shares the parent’s existing data instead of duplicating it. Both can read the same underlying data, while changes made to one remain isolated from the other. But when the branch modifies data, the storage layer creates a new version of the affected data for that branch, while unchanged data remains shared with the parent.
Lakebase uses this copy-on-write approach to create database branches without duplicating the entire parent database. As a result, each branch requires additional storage only for the data that diverges from its parent.
Consider a 40 GB database. With a traditional full copy, creating a developer branch and a pull request branch requires an additional 80 GB of storage. However, with copy-on-write, both branches initially share the parent’s data and consume additional storage only when they diverge.

As shown in the diagram above, if the changes in the developer branch are just 1.6 MB and the changes in the PR branch are just 4 MB, the two branches add only about 5.6 MB of storage. Traditional copies duplicate the entire database for each branch, while copy-on-write branches share unchanged data and store only their changes.
The same principle applies when the parent changes after a branch is created. The branch continues to reference the original version of unchanged data, while the parent writes new versions of the pages it modifies. This allows the two branches to change independently without duplicating unchanged data.
What Database Branching Makes Possible
Once you have database branching, several development workflows become much easier to implement:
Production-like baselines
You can create database branches from a protected production snapshot so every developer and CI job starts from the same known state. That lets you test migrations against realistic data, existing constraints, and production-scale tables instead of an empty local database or stale staging environment.
For example, a migration like ALTER TABLE orders ADD COLUMN customer_id UUID NOT NULL may work on an empty database but fail against millions of existing orders. Testing it on a production-like branch exposes that problem before the migration reaches staging or production. When the branch becomes stale, you can delete it and create a fresh one from the same baseline.
Per-PR isolation
You can give every pull request its own database environment. CI creates the branch when the PR opens, applies the proposed migrations, and runs integration tests against it. When the PR closes, the pipeline deletes the branch.
This means two developers can make conflicting schema changes without affecting each other's tests. A PR that adds a column, changes a constraint, or modifies an index gets its own database state, so CI tests the change in isolation rather than against whatever another developer is doing in staging.
Easier failure recovery
Branches also make it easier to isolate failed migrations and experiments. If a backfill produces unexpected results, a test corrupts data, or a migration leaves a branch in a bad state, you can discard the affected branch and create a fresh one from the parent instead of continuing to work with a contaminated development environment.
For example, you can safely test a destructive operation such as DELETE FROM orders WHERE created_at < ... on a branch, inspect the results, and discard the branch when you're done. The parent database remains untouched throughout.
For developers and DevOps teams, these benefits are already compelling. However, if you're building or running AI agents, database branching becomes important at an entirely different scale.