stolon

Docs

Make a database, branch it, merge it back.

Six steps from nothing to a merged pull request, then the reference. For one task at a time, see the guides.

Get started

  1. Create a database

    Sign up, confirm your address and name a database.

  2. Create an app password

    On the database's page, Create an app password. It is shown once, so copy it then. Revoke one to cut it off.

  3. Connect

    Any Postgres client. The database name picks the branch: <database>.<branch>. Your app uses main.

    $ psql "postgresql://app:<password>@db.stolondb.com/acme.main"
  4. Branch

    A branch is an instant copy of the whole database. Connect to it by name and change anything.

    acme.main=> CREATE DATABASE price_change; CREATE DATABASE $ psql "postgresql://app:<password>@db.stolondb.com/acme.price_change"
  5. Review it as a pull request

    In the console, a branch with changes is a pull request: every row it added, changed or removed, and anything that conflicts with main.

  6. Merge

    Merge from the pull request, or from SQL. DRY RUN shows what would land without landing it.

    acme.main=> MERGE DATABASE price_change INTO main DRY RUN; acme.main=> MERGE DATABASE price_change INTO main;

Connect

Any Postgres driver connects unchanged, with the branch as the database name:

# python >>> import psycopg >>> psycopg.connect("postgresql://app:PASSWORD@HOST:PORT/acme.staging") # sqlalchemy + alembic $ DATABASE_URL="postgresql+psycopg://app:$PASSWORD@$HOST:$PORT/acme.staging" alembic upgrade head

Short-lived tokens, for CI and agents

Exchange an API key from your Account page for a token scoped to one database, valid for up to 24 hours. Use it as both user and password.

$ TOKEN=$(curl -s https://console.stolondb.com/v1/tokens \ -H "Authorization: Bearer $STOLON_API_KEY" \ -d '{"store":"acme","sub":"ci","scopes":["query","data","branch"],"ttl":3600}' | jq -r .token) $ psql "postgresql://$TOKEN:$TOKEN@db.stolondb.com/acme.main"

scopes: query (read), data (write), branch (create, merge and drop branches). ttl is seconds, up to 86,400.

Postgres compatibility

Every SQL behavior is checked against a live PostgreSQL on every test run: 210 script families, compared on rows, command tags, error codes and transaction status.

AreaDetail
Wire protocolworks simple and extended, binary parameters and results, SCRAM-SHA-256
Clientsworks psql, psycopg2, psycopg3, pgJDBC
ORMs and migrationsworks SQLAlchemy 2.0, Alembic including autogenerate
Queriesworks all join kinds, subqueries, CTEs incl. WITH RECURSIVE, set operations, GROUP BY/HAVING/FILTER, DISTINCT ON
Window functionsworks row_number, rank, lag, lead, aggregates with OVER, named windows, ROWS/RANGE/GROUPS frames
Transactionsworks savepoints, Postgres abort semantics
Constraintsworks NOT NULL, UNIQUE, CHECK, FOREIGN KEY with every action, enforced
Schemaworks ALTER TABLE, views, sequences, SERIAL/IDENTITY, indexes incl. partial, generated columns, enums, schemas
Upsertsworks ON CONFLICT … DO UPDATE / DO NOTHING, ON CONSTRAINT
Roles, grants, RLSworks CREATE ROLE, SET ROLE, GRANT/REVOKE, row-level security policies
PL/pgSQL, procedures, triggersworks functions, DO blocks, cursors, exception blocks, row and statement triggers
Typesworks integers, numeric, floats, text, boolean, dates, timestamps, intervals, uuid, bytea, json/jsonb, arrays
Functionsworks string, regex, date/time (date_trunc, to_char, AT TIME ZONE), JSON (->>, jsonb_set, jsonb_agg), generate_series, LANGUAGE sql functions
Extensionsworks vector (pgvector, ivfflat, hnsw), pgcrypto, citext, hstore, pg_trgm, uuid-ossp, unaccent, fuzzystrmatch
Toolingworks psql backslash commands (\d, \dt, \l), COPY and \copy in text and CSV, pg_catalog and information_schema

Differences from Postgres

Moving data in and out

# in: a real pg_dump $ pg_dump --no-owner mydb | stolon-db /path/to/store --import main # out: back to Postgres, any branch $ stolon-db /path/to/store --export staging | psql -d restored # will this run here? ask before you migrate $ cat queries.sql | stolon-db /path/to/store --compat-check main

On a hosted database, import and export from the console or its HTTP API. An import lands in a new branch.

Branching and history

StatementWhat it does
CREATE DATABASE binstant fork of the connected branch: schema, data and history
CREATE DATABASE b (schema only)the structure, every table empty
DROP DATABASE bdrops it; a protected branch refuses
MERGE DATABASE b INTO mainmerges the branch back (below)
SELECT … FOR SYSTEM_TIME AS OF tany table at any moment, on any branch
COMPARE DATABASE a [AS OF t] WITH b [AS OF t]rows added, removed and modified between two states, then ROWS or CELLS

A branch can start from a past moment, and can be restored to one. The console's Restore keeps the present as a branch first, so a restore can be undone. The history guide walks through it. Protect, expire and reset are in the console and the HTTP API.

Merging

MERGE DATABASE b INTO main [RESOLVE OURS|THEIRS] [DRY RUN]

Durability

Access control

Agents and MCP

Every database serves MCP at https://console.stolondb.com/mcp/<database>. An agent gets a key-value store where every write is a version, and can review and merge the database's branches. Add it to Claude, ChatGPT, Claude Code or Cursor and sign in; the MCP guide has each.

A branch can also carry references, such as a git SHA or an image digest, and decisions recording why it is the way it is. Both fork, merge and roll back with the data.

The console

Open a database from the dashboard.

The console overview: main, what your app connects to, above a list of branches, each with what it changed and a button to review its changes.
Fig. 1 — the overview: main and its branches. · enlarge
The console's version tree: every branch drawn at the depth it forked from, beside the database's health.
Fig. 2 — the version tree, beside the database's health. · enlarge

A read token sees branches, the version tree and health; query also sees rows, history and pull requests.

Deployment