Skip to Content
DatabasesZero-Friction Branching

Database Branching & Zero-Friction Workflows

In modern application development, managing database states alongside code changes is one of the biggest bottlenecks.

Nanokit Database Branching solves this by introducing a unified, context-aware database orchestration layer. It ensures that your data layer always follows your application’s state—automatically aligned with your active Git branches.


What is Database Branching?

Just as Git allows you to branch your code to work on features in isolation, Nanokit allows you to branch your database.

When you switch Git branches, Nanokit dynamically redirects your microservices to an isolated, branch-specific database instance containing a copy of your parent data. You don’t need to manually import dumps, write custom scripts, or edit connection strings.

┌─── [ Git: main ] ────────► DB: main_db (Production/Staging) Git Branch Switch ┼─── [ Git: feat-login ] ──► DB: main_db-feat-login (Isolated Copy) └─── [ Git: feat-billing ] ─► DB: main_db-feat-billing (Isolated Copy)

Key Benefits

Implementing database branching inside your team’s workflow unlocks game-changing advantages:

  • ⚡ Instant Velocity: Instantly clone a production-like database with millions of rows in seconds. Say goodbye to waiting hours for manual database backup restoration.
  • 🛡️ Absolute Safety: Test destructive schema migrations, execute experimental queries, or seed test data without any risk of polluting the production database or interrupting other developers.
  • 🔌 Zero-Config Autopilot: Connection strings (like DATABASE_URL or NANOKIT_DB_POSTGRES_URL) are automatically resolved and injected at runtime based on the active Git branch. The application code remains completely agnostic.
  • 🛠️ Parallel Development: Multiple developers can run different database migrations (e.g. prisma migrate, knex migrate) concurrently. There are no conflicts on shared staging environments.
  • 🧪 Deterministic CI/CD Pipelines: Spin up an atomic, pre-seeded database clone for each Pull Request, run integration tests, and automatically tear it down once the PR is merged.

How it Works

Nanokit manages database branches seamlessly across both cloud-native platforms and local development environments:

1. Cloud-Native Managed Branching (Cloud Mode) — planned (roadmap.v3)

Managed cloud-provider branching via native platform APIs is parked pending real-account validation (roadmap.v3) and is not available today:

  • Neon (Postgres): would use Neon’s Copy-on-Write (CoW) technology — instant branches, zero additional storage.
  • Turso (SQLite/LibSQL): would use the LibSQL seed/fork API to copy structure and data.

The near-term in-philosophy target is a self-hosted libSQL (sqld) container engine (roadmap.v2), branched by Nanokit’s own volume-clone strategy (below) rather than a cloud API. See Providers → Roadmap.

2. The Local Logical Overlay (Local Mode)

For local development or standard VPS deploys, Nanokit simulates cloud branching behaviors using Docker-level optimizations:

  1. Volume Capture: It detects the parent data volume (e.g., nk-postgres-data-production).
  2. Atomic Clone: It spawns an optimized, temporary alpine bridge container to clone the volume data atomically and quickly.
  3. Container Isolation: It boots up a new database container (e.g., nk-<project>-local-<dbName>-<branchName>) mounted to the cloned volume.
  4. Dynamic Network Mapping: The local gateway routes traffic for that specific service to the isolated container automatically.

Configuration

To enable branching for a database, simply configure the branching block in your nanokit.yml:

databases: nk_db_postgres: engine: postgres mode: local # or 'cloud' rootPassword: ${DB_PASSWORD_POSTGRES} branching: enabled: true # Enables dynamic branch context switching strategy: snapshot # 'snapshot' clones data; 'empty' starts with fresh schema

Naming Conventions & Isolation

To maintain environment-agnostic code:

  • Logical Name: The name defined in nanokit.yml (e.g. nk_db_postgres). Your services query this name.
  • Physical Name: Automatically resolved as <LogicalName>-<BranchName> (e.g. nk_db_postgres-feat-login).

Nanokit dynamically translates the logical host inside your connection strings to point to the physical branch container.


CLI Lifecycle Commands

You can manage the lifecycle of your database branches directly from your terminal using the Nanokit CLI:

Forking a Database

To create a physical branch and clone the data from the parent environment:

nkapp db fork <db-name>

Switching Branch Contexts

If you want to manually point your environment to a specific database branch:

nkapp db switch <db-name> --branch <target-branch-name>

Note: Your application service environment variables will automatically update and bind on the next nkapp up.

Resetting a Branch

To discard all experimental data changes on a branch and re-clone fresh data from the parent:

nkapp db reset <db-name>

Reset Warning: Resetting a database branch will permanently destroy all changes made on that specific branch. Make sure to commit or backup any critical schema migrations before executing this command.


Troubleshooting: “My data disappeared after switching branches”

This is the single most common point of confusion with branching, and almost always a false alarm: your data is safe, you are simply looking at a different branch’s database.

Symptom

You were working on acme-shop, signed in as jane.doe@example.com, then checked out another Git branch (say feature-checkout), ran nkapp up, and now your app reports the account as missing, the users table is empty, or seeded records are gone.

Why it happens

Each branch gets its own isolated data volume. Switching Git branches does not migrate data between them, and the running database container always follows the branch of your last nkapp up. So if your account was created on main but the container currently running is the feature-checkout clone (created fresh, or cloned at a point before the account existed), your services query an isolated DB that genuinely does not contain that row.

A typical local layout for the app_db logical database looks like this:

VolumeBranchContents
nk-postgres-data-app_dbbase / mainjane.doe@example.com
nk-postgres-data-app_db-feature-checkoutfeature-checkoutjane.doe@example.com (cloned)
nk-postgres-data-app_db-experiment-uiexperiment-ui❌ empty (started with strategy: empty)

If the container mounted right now is the experiment-ui one, the account looks “gone” — it just lives in a different volume.

Diagnose

List the per-branch volumes and check which one the running container actually mounts:

# All data volumes for the logical database docker volume ls --format '{{.Name}}' | grep app_db # Which volume the currently-running container is bound to docker inspect nk-acme-shop-local-app_db-experiment-ui \ --format '{{range .Mounts}}{{.Name}} -> {{.Destination}}{{println}}{{end}}'

If the mounted volume’s branch suffix doesn’t match the branch where you created your data, that is the whole issue.

Fix

Bring the database back in sync with the branch that holds your data. From the branch you want (e.g. main):

nkapp up -e local

This reconciles the running container onto that branch’s volume. To point at a specific branch without checking it out, use:

nkapp db switch app_db --branch main

Do not run clean, destroy -v, or db prune while looking for “lost” data. Those commands remove volumes — and the branch volumes are exactly where your records still live. Confirm which volume holds your data (steps above) before any destructive operation.


Populating an empty database branch

A branch can legitimately start empty: it was created with strategy: empty, the parent had no data when it was cloned, or you reset it. The canonical way to seed a branch in Nanokit is nkapp db pull — it streams real data from another environment into the active branch.

db pull is the dedicated seed command. Point it at a source environment (staging, production, …) and it populates the local branch with that data:

nkapp db pull app_db --from staging --branch feature-checkout

--from defaults to production; --branch targets a specific local branch (the active one if omitted).

db pull overwrites the target branch’s data with the source. Use --force only when you intend to drop whatever is currently in the local branch.

Alternative: clone from the parent (snapshot)

If the parent branch already holds the data you want, re-clone it locally without touching a remote. db reset discards the active branch’s contents and pulls a fresh copy from its parent (switch to the branch first with db switch if needed):

nkapp db switch app_db --branch feature-checkout nkapp db reset app_db

To make new branches inherit parent data automatically, set the snapshot strategy in nanokit.yml (the default for new branches) instead of empty:

databases: app_db: engine: postgres branching: enabled: true strategy: snapshot # clones parent data into each new branch

Alternative: fork an environment into a fresh branch

db fork creates the branch and clones a source environment’s data in one step (add --skipPull to create it schema-only, with no data copy):

nkapp db fork app_db --sourceEnv production

Inserting rows by hand

For ad-hoc records, open an interactive shell straight into the database container. Nanokit auto-selects the right client (psql for Postgres, mongosh for Mongo, redis-cli for Redis, …):

nkapp shell app_db --db # then, inside psql: # INSERT INTO users (email) VALUES ('jane.doe@example.com');

Whichever method you choose, the data lands in the active branch’s isolated volume only. Other branches stay untouched, which is exactly the isolation guarantee branching is built to provide.