Branches
Learn about database branches in AxiomDB—isolated Postgres instances you can spin up, work on, and delete with ease.
Branches
In AxiomDB, a branch is an independent, fully connectable Postgres database instance that lives inside a project. Think of it like branching in Git, but for your database! Each branch comes with its own database schema, separate access credentials, standalone performance metrics, and a configurable lifespan. Branches are the core building block of AxiomDB—when your application connects to a database, it's always pointing to a specific branch.
When you create a project, AxiomDB automatically sets up a main branch. This main branch acts as your source of truth; it is protected, has an infinite lifespan, and can never be deleted. Any other branches you spin up are ephemeral: you create them from the main branch (or another child branch), they copy the source database's schema, and they are automatically cleaned up once they expire.
Branch Anatomy
Here is a breakdown of what makes up an individual database branch in AxiomDB:
⚛ Branch Structure Breakdown ⚛
=========================================================
● DATABASE ━━━━━━━━► sq_<key>_<env>_br_<slug>
● ROLES ━━━━━━━━━━━► owner (DDL/Admin), rw (App read/write), ro (Read-only)
● SECRETS ━━━━━━━━━► DATABASE_URL (Pooled), DIRECT_URL (Bypass)
● METRICS ━━━━━━━━━► Live connections, disk usage, CPU seconds
● LIFESPAN ━━━━━━━━► 7 days / 1 month / 6 months / 1 year / forever
● STATUS ━━━━━━━━━━► active / expired / deleting / deleted / failed
● DATA ━━━━━━━━━━━━► ID, Slug, Source branch, Creator, Timestamps
=========================================================Database Naming Convention
AxiomDB names your databases and user roles automatically. The names are based on your project's unique app_key, the environment (env), and the branch's name (slug).
Main Branch
- Database Name:
sq_<app_key>_<env> - For example:
sq_payments_prod
Child Branches
- Database Name:
sq_<app_key>_<env>_br_<branch_slug> - For example:
sq_payments_prod_br_fix-checkout-bug
Roles & Access Levels
To keep permissions secure and simple, each database comes with three standard access roles:
| Access Role | Naming Pattern | Example | What it can do |
|---|---|---|---|
| Owner | <app_key>_<env>_owner | payments_prod_owner | Full administrative rights, including creating tables and altering the schema. |
| Read-Write | <app_key>_<env>_rw | payments_prod_rw | Read and write data (SELECT, INSERT, UPDATE, DELETE)—ideal for runtime apps. |
| Read-Only | <app_key>_<env>_ro | payments_prod_ro | Can only read data (SELECT)—perfect for analytics tools. |
Shared Roles
Roles are shared across the whole project. The payments_prod_rw role exists once in your database cluster and has permissions configured to access any database branch belonging to that project.
Lifespans
Except for the main branch, every branch has a set lifespan. Once this time runs out, the branch expires and is automatically deleted to keep your infrastructure clean.
Available Lifespan Settings
7d(7 Days): Best for quick feature branches or testing out experiments.1m(30 Days): Ideal for standard sprint cycles and QA verification.6m(180 Days): Good for long-lived staging environments.1y(365 Days): Used for annual compliance verification or long-term environments.forever(No Expiration): Reserved for themainbranch only.
Lifespan Rules
- The
mainbranch is locked toforeverand cannot expire. - Ephemeral child branches can use any lifespan option, but they cannot use
forever(unless authorized). - The default lifespan of a new branch is set by the project environment:
devenvironments default to7dstagingenvironments default to1mprodenvironments default to1y
- You can extend a branch's lifespan before it runs out using our API:
curl -X PATCH https://gateway.axiomdb.io/v1/branches/br_x1y2z3 \ -H "Authorization: Bearer paseto_v4_..." \ -H "Content-Type: application/json" \ -d '{ "lifespan": "6m" }' - If you want to speed up cleanup, you can also shorten a lifespan:
curl -X PATCH https://gateway.axiomdb.io/v1/branches/br_x1y2z3 \ -H "Authorization: Bearer paseto_v4_..." \ -H "Content-Type: application/json" \ -d '{ "lifespan": "7d" }'
Expiration Timeline
Here is how extending a branch's lifespan affects its expiration:
⚡ Expiration Timeline ⚡
========================================================================
[Created] ──► [Active: 7-day clock starts]
│
├─► (Day 3) Extension requested to "6m"
│ │
│ ▼
│ [Clock reset to 6 months]
│
├─► (Day 7) ──► (Original expiry bypassed!)
│
└─► (6 Months) ──► [Branch Expires & Deletes]
========================================================================Protected Branch Rules
Because the main branch holds your primary schema, we protect it with strict rules:
- Creation: Handled automatically when you create a project. You cannot create a second main branch.
- Deletion: Strictly forbidden. Attempts to delete
mainreturn a403 Forbiddenerror. - Lifespan: Locked to
foreverand cannot be modified. - Rename: Cannot rename the
mainbranch. - Schema & Data Changes: Fully allowed so you can develop and run migrations.
Branch Status Values
A branch progresses through several lifecycle states:
⚡ Branch Lifecycle Status Transitions ⚡
========================================================================
[creating] ──► [active] ◄──► [expired] ──► [deleting] ──► [deleted]
(extend) │
└──► [failed]
========================================================================Here is what each status code means:
creating: We are provisioning the database and copying the schema from the parent.active: The branch is fully healthy, online, and ready for you to connect.expired: The lifespan has elapsed. The branch becomes read-only and enters a 5-minute grace period before deletion.deleting: We are tearing down the database, dropping roles, and clean up.deleted: The branch has been completely removed from the cluster.failed: Something went wrong during creation or deletion. The database is locked and flags an operator.
About Expired Branches
Once a branch expires, it goes read-only for 5 minutes. You can still extend the lifespan during this window to bring it back to active. Once those 5 minutes are up, the branch is permanently deleted and the data cannot be recovered.
Branch Creation Flow
Spinning up a new branch is as simple as making an API call:
curl -X POST https://gateway.axiomdb.io/v1/projects/proj_a1b2c3d4/branches \
-H "Authorization: Bearer paseto_v4_..." \
-H "Content-Type: application/json" \
-d '{
"slug": "fix-checkout-bug",
"lifespan": "7d",
"source_branch_id": "br_main"
}'Internal Sequence
Here is what AxiomDB does behind the scenes when you request a new branch:
⚡ Creation Sequence ⚡
========================================================================
[Client] ──(1. POST /branches)──► [Gateway]
│
▼ (2. Verify Token & Limits)
[Control Plane]
│
▼ (3. Enqueue Job)
[square-dbctl]
│
├─► (4. CREATE DATABASE)
├─► (5. Copy Schema)
└─► (6. Grant Role Access)
│
[Client] ◄──(7. 201 Created)──────────┘
========================================================================Schema Copying
To make branching lightning fast, AxiomDB copies only the schema structure, not the actual data records. The workflow:
- Runs
pg_dump --schema-onlyon the parent source database. - Applies the generated schema definitions to your new child database.
- Verifies the schema match by running a check query:
SELECT COUNT(*) AS table_count FROM information_schema.tables WHERE table_schema = 'public';
If the table count does not match, we mark the branch as failed and alert our system.
Branch Credentials
When you connect, you have two connection URLs:
DATABASE_URL(Port 6432): Routes through PgBouncer pooling for runtime application traffic.DIRECT_URL(Port 5432): Direct Postgres connection for migrations and admin tasks.
Connection Payload Example
{
"branch_id": "br_x1y2z3",
"slug": "fix-checkout-bug",
"database_url": "postgresql://payments_prod_rw:<password>@gateway.axiomdb.io:6432/sq_payments_prod_br_fix-checkout-bug?sslmode=require",
"direct_url": "postgresql://payments_prod_owner:<password>@gateway.axiomdb.io:5432/sq_payments_prod_br_fix-checkout-bug?sslmode=require",
"role": "payments_prod_rw",
"database_name": "sq_payments_prod_br_fix-checkout-bug"
}Check out our Connections Guide for more details on integrating these URLs with your frameworks.
Branch Metrics
AxiomDB tracks performance metrics for each branch. They look like this:
{
"branch_id": "br_x1y2z3",
"metrics": {
"storage_bytes": 188743680,
"storage_gib": 0.176,
"active_connections": 4,
"total_connections": 127,
"cpu_seconds": 42.7,
"rows_inserted": 150000,
"rows_updated": 32000,
"rows_deleted": 500,
"cache_hit_ratio": 0.97,
"transactions_committed": 890000,
"transactions_rolled_back": 120
},
"sampled_at": "2025-03-20T14:00:00Z"
}Storage Query
We measure database size using:
SELECT pg_database_size(current_database()) AS storage_bytes;Cache Hit Ratio
We calculate cache efficiency using:
SELECT
sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) AS cache_hit_ratio
FROM pg_statio_user_tables;Metric Updates
Metrics are checked and cached every 60 seconds. We store historical performance data for up to 30 days.
Branch Limits
To prevent resource abuse, we enforce a few basic guardrails:
- Max active branches per project:
10(includingmain). - Slug length limits: Between
3and48characters (lowercase letters, numbers, and hyphens only). - Max database name length:
63characters (standard Postgres limit). - Schema copy timeout:
5 minutes(any copy taking longer will fail the branch).
Checking Limits
Before spinning up a new branch, our gateway counts active instances:
SELECT COUNT(*) AS active_branches
FROM branches
WHERE project_id = $1 AND status IN ('creating', 'active', 'expired');If you've hit the limit, you'll receive a 429 Too Many Requests status code.
Source Branch Inheritance
You don't have to branch from main every time. You can branch from any existing child branch, allowing you to stack branches:
⚡ Branch Stacking Example ⚡
========================================================================
[main] (Protected, forever)
└─► [feat-payments] (1m)
├─► [fix-payment-bug] (7d)
└─► [payment-tests] (7d)
========================================================================When you branch from another child branch, we copy the schema precisely as it exists on that source branch at that moment—including any unmerged migrations.
API Reference
Here is the quick API cheatsheet for managing branches:
| HTTP Method | Endpoint | What it does |
|---|---|---|
POST | /v1/projects/:pid/branches | Spins up a new branch. |
GET | /v1/projects/:pid/branches | Lists all branches in a project. |
GET | /v1/branches/:bid | Gets details for a single branch. |
PATCH | /v1/branches/:bid | Updates branch settings (lifespan or slug). |
DELETE | /v1/branches/:bid | Deletes a branch. |
GET | /v1/branches/:bid/credentials | Retrieves connection URLs. |
GET | /v1/branches/:bid/metrics | Fetches performance metrics. |
POST | /v1/branches/:bid/extend | Extends a branch's lifespan. |
How is this guide?
