Skip to content
日本語

Beta

MySQL / PostgreSQL · ZFS · Apache-2.0

Database branching on your own servers. No cloud lock-in.

DB Branch makes MySQL and PostgreSQL branches from ZFS snapshots. A branch is ready in seconds however large the Base is, and your data never leaves your network.

The core is free and open source under Apache-2.0. Install it from the apt repository.

0.5sPostgreSQL branch
3sMySQL branch
0Bcopied at creation

Measured on our 3-node test cluster, from the API call until the branch accepts connections. Thanks to CoW, the time does not grow with the size of the base.

How bases, snapshots and branches relateThe production base prod is refreshed daily and masked into prod-masked, which takes a snapshot each time. The branches pr-1287, feature-x and qa-nightly are made from those snapshots.prodraw data (quarantined) · refreshed every 24hmasked withapproved rulesprod-maskeds20261109030012s20261110030009s20261111030011pr-1287feature-xqa-nightlyCoW branches use space only for what they writegone when they expire
Top: raw production data. Middle: the masked base. Bottom: branches made from it.

See it work

A Branch in seconds, a reset in seconds.

Terminal recording: a Branch is created from a 1 GiB PostgreSQL Base, its data is truncated, and a reset brings the data back.
Create a Branch from a 1 GiB PostgreSQL Base, TRUNCATE the data, then reset the Branch: the data is back. Real terminal output from our 3-node test cluster.
The user page of the web console: a list of Bases, a name field and the connection details of a new Branch.The user page of the web console: a list of Bases, a name field and the connection details of a new Branch.
The user page: pick a Base, name a Branch and copy the connection details. Sample data.

The console

See every Base, Branch and node in the browser.

The web console is served by every node and uses the same API as the CLI.

The admin Branches screen: a table of Branches with their Base, engine, endpoint, TTL and storage.The admin Branches screen: a table of Branches with their Base, engine, endpoint, TTL and storage.
Branches: every Branch with its Base, engine, endpoint, TTL and storage use. Sample data.
The admin Bases screen: each Base as a card with a timeline of Snapshots and the Branches cloned from them.The admin Bases screen: each Base as a card with a timeline of Snapshots and the Branches cloned from them.
Bases: each Base with its Snapshots and the Branches cloned from them. Sample data.
The admin Cluster screen: three nodes with pool space, load charts and Branches, and a table of installed engines.The admin Cluster screen: three nodes with pool space, load charts and Branches, and a table of installed engines.
Cluster: pool space, load and Branches on each node. Sample data.

Architecture

Built from parts you already run.

Branches are ZFS copy-on-write clones, connections can go through ProxySQL, and every branch is a stock MySQL or PostgreSQL server. Nothing proprietary sits in the data path, so your ops team can debug it with the tools they already know: zfs, ProxySQL, mysql and psql.

DB Branch architectureAn app, CI job or developer connects either through ProxySQL, which is optional and routes by user name, or directly to a branch port. Each branch is a mysqld or postgres process on a ZFS clone of a Base snapshot. The same layout runs on each of three nodes, whose state is kept by a Raft control plane. The console, CLI, REST API and MCP control it.Each node (×3)App · CI · developerMySQL or PostgreSQL clientProxySQL (optional)user name = branch namedirect tothe branch portBranch databasesmysqld / postgres per branchpr-1287feature-xqa-nightlyZFS clones (CoW)cloneBase snapshotControl planeRaft · 3 nodesConsole · CLI · API · MCPcontrol everything

How it works

Build one base, and every branch after that takes seconds.

A branch is a ZFS clone of a base snapshot. Clones are copy-on-write (CoW): creating one copies no data, and a branch takes new space only for the blocks it writes.

Step 1

Build a base from a dump or from production

Load an SQL dump, or pull from production or a replica on a schedule. The base is the read-only original.

Step 2

Keep each point in time

Every load takes a snapshot. Branches can start from older snapshots too.

Step 3

One per person, one per pull request

A branch is its own database server. Changes to it never reach the original, and resetting or deleting it takes seconds.

# Install on each node and point it at a ZFS pool
$ sudo apt install db-branch
$ sudo db-branch init --pool tank

# Build a base from a dump, then branch from it
$ db-branch base create prod --runtime postgres@18.6 --dump /srv/prod.sql.gz
$ db-branch branch create feature-x --base prod --ttl 72h

Install from the apt repository; see Getting started for the steps.

CI

A fresh database for every pull request.

Create a branch when a pull request opens and delete it when the pull request closes. This GitHub Actions workflow calls the REST API with a token, and the branch expires by itself if a close event is missed.

# .github/workflows/db-branch.yml
on:
  pull_request:
    types: [opened, closed]
jobs:
  db:
    runs-on: self-hosted
    env:
      DBB: https://node1:7080/v1
      AUTH: "Authorization: Bearer ${{ secrets.DBB_TOKEN }}"
      BRANCH: pr-${{ github.event.number }}
    steps:
      - if: github.event.action == 'opened'
        run: |
          OP=$(curl -fsS -H "$AUTH" -H 'Content-Type: application/json' \
            -d "{\"name\":\"$BRANCH\",\"base\":\"prod\",\"ttl\":\"72h\"}" $DBB/branches | jq -r .id)
          while [ "$(curl -fsS -H "$AUTH" $DBB/operations/$OP | jq -r .state)" = running ]; do sleep 1; done
          export DATABASE_URL=$(curl -fsS -H "$AUTH" $DBB/branches/$BRANCH/dsn | jq -r .dsn)
          npm test
      - if: github.event.action == 'closed'
        run: curl -fsS -X DELETE -H "$AUTH" $DBB/branches/$BRANCH

Use a runner that can reach the cluster on port 7080 and trusts its certificate (tls.pem). A branches token is enough; keep it in a repository secret. See the API docs for every field.

Connect

Connect directly, or through ProxySQL on any node.

Every node runs ProxySQL 3.x (the free db-branch-proxysql module), so connecting to any node reaches every branch in the cluster. Client ports are 6033 for MySQL and 6133 for PostgreSQL.

Direct

Use the host, port and user of the branch server. db-branch branch dsn prints the URL with the password.

By user name

Each branch has its own user. The app changes only the user in its connection string.

By query comment

Add /*db_branch='name'*/ to a query to pick the branch. A middleware can add it per request from an HTTP header. The comment wins over the user name. A transaction never switches branch midway.

# Same host, different user: the user picks the branch
mysql://feature-x:<password>@node1:6033/app
mysql://pr-1287:<password>@node1:6033/app

-- A comment on the query picks the branch
SELECT * FROM users WHERE id = 1 /*db_branch='feature-x'*/;

The mysql CLI strips comments unless you start it with -c.

Features

The features a team uses every day are built in.

Engines

MySQL and PostgreSQL

Pick the runtime version for a base. Its branches run on a server of the same version.

Scheduled refresh

Pull from production automatically

Set the source and an interval: DB Branch dumps, loads and snapshots for you. The dump is deleted as soon as it is loaded.

Cluster

Redundant on three or more nodes

Raft keeps the state on every node, so any node takes commands. Snapshots are replicated between nodes.

Interfaces

Console, CLI and API

See and manage everything in the web console. From CI, create and delete a branch per pull request with the CLI or the REST API.

AI agents

Hand branches over MCP

Agents such as Claude Code make a branch of their own and try their SQL in it without touching production.

Storage

Stored with lz4 compression

Bases and branches are stored with ZFS lz4 compression. Because branches are CoW clones, ten branches of one base take little more than the base itself until they start writing.

Reset

Back to the start in seconds

Each branch keeps its starting point as a CoW snapshot. A reset rolls back to that snapshot, so it finishes in seconds and runs no SQL again.

Masking

Free masking tools included

Build a masked base with Greenmask or PostgreSQL Anonymizer for PostgreSQL, or Greenmask (beta) for MySQL.

Operations

Expiry, notifications, backups

Branches expire and clean themselves up. Notifications go to Slack or a webhook, and the cluster state is backed up.

Production data

Keep raw data to yourself and hand out only masked copies.

In the planned Business plan, pulling from production, masking, checking and handing out run as one chain. A change to the rules needs approval from someone other than its author.

Pull

Refresh from production or a replica on a schedule.

Quarantine

Only admins touch the raw base, and no one can branch from it.

Mask

After every refresh, mask again with the approved rules only.

Check

If a check you defined finds a single row, the result is thrown away.

Hand out

Branches come only from snapshots that passed the checks.

You decide what to mask, how, and whether the result is good enough. DB Branch carries out those decisions and keeps a record of them; it does not guarantee the masked result.

Requirements

What it runs on

We have not set official minimums yet; the table shows what we test on.

OSLinux that installs packages with apt. Tested on Ubuntu 24.04 LTS.
StorageA ZFS pool for bases and branches (tested with OpenZFS 2.2). Datasets use lz4 compression.
DatabasesTested with MySQL 8.0 and 8.4, and PostgreSQL 18.
NodesRuns on one machine. Three or more make a cluster that keeps working when one node is down.
CPU and memoryOur test cluster: one node with 6 vCPU and 11 GB, and two with 2 vCPU and 3 GB.
Memory per branchEach branch server gets a 128 MB cache by default (InnoDB buffer pool or shared_buffers), adjustable in Settings.
DiskThe base data, compressed with lz4, plus whatever the branches write. Unchanged blocks are shared through CoW.