# Branchable Postgres Databases | Volcano

URL: https://volcano.dev/features/postgres

> Branchable PostgreSQL 16 through 18 on Volcano. Fork a copy-on-write branch of your data in under a minute for every pull request or migration, connect with psql, your ORM or the SDK, and add Superagent backups with point-in-time restore.

## Branchable Postgres, nothing to operate

PostgreSQL 16 through 18 that branches in under a minute: a copy-on-write fork of your data with its own connection string, for every pull request, migration or test run. Connect with psql or your ORM, let row-level security decide what each user sees, and add point-in-time restore on Superagent.

```console
$ volcano cloud databases branches create app pr_482 --ttl 72h
✓ Branch 'pr_482' of database 'app' created
$ volcano cloud databases branches get app pr_482 --show-connection-string
Status: active   Connection string: postgresql://volcano_client_…
# app's data, its own password, and gone on its own in 72 hours
```

## Starting a database is easy. Keeping one is not.

The work isn't provisioning. It's sizing the instance, putting a pooler in front of it, keeping the connection string out of the browser, writing an authorization layer because Postgres has no idea who your users are, and having somewhere to try a migration before it runs against production.

Volcano hands you ordinary PostgreSQL 16 through 18 on compute that scales with load inside the tier you pick. Volcano establishes who is asking before your first statement runs, so auth.uid() works inside your policies and the database itself decides which rows a request can see. A branch forks a copy of the data in under a minute; on Superagent, a restore takes the database back to a backup or a moment in time without changing its connection string.

## A copy of production in under a minute

*Branches*

A branch is a copy-on-write fork of a database: the same schema, rows, roles and policies, behind its own connection string and password. Writes never reach the parent, storage is charged only for what the branch changes, and every branch expires on a deadline between one hour and 30 days, so a forgotten copy of production does not linger.

- A database for every pull request or test run
- Reset to the parent's current data in one call
- Charged for what it changes, not what it copied

```console
$ volcano cloud databases branches create app try_004 --ttl 24h
Status: provisioning · usually active in under a minute
$ volcano cloud databases branches get app try_004 --show-connection-string
Status: active   Connection string: postgresql://volcano_client_…
$ psql "$BRANCH_URL" -f volcano/migrations/004_split_names.sql
ALTER TABLE · UPDATE 1284 · the parent never saw it
$ volcano cloud databases branches reset app try_004 --yes
✓ re-forked from app as it is now · same connection string
```

## Two ways in, one set of rules

*Access*

Query from the browser with the SDK, or open a connection on port 5432 with psql, your ORM or any Postgres driver — it is a normal Postgres endpoint over TLS, not an HTTP API pretending to be one. Whichever way a query arrives, Volcano establishes who is asking before your first statement, so your policies decide what comes back.

- psql, Prisma, Sequelize, TypeORM or any driver
- The SDK queries from the browser, with no API tier
- One string, three access levels: admin, a given user, anonymous

```console
# one query, three callers, one policy
admin · migrations       → 1284
signed-in user 8f1c…     →   12
anonymous visitor        →    0
the policy is the only thing deciding
```

## Size each database for its job

*Scale*

Six tiers from volcano-db-xs to volcano-db-2xl, with compute that moves with load inside the tier you choose. Staging, a load test and production can each get a tier of their own instead of sharing one instance.

- Six tiers, volcano-db-xs through volcano-db-2xl
- Compute moves with load inside the tier
- A database per environment or tenant

Image: The Volcano dashboard listing databases with their region, Postgres version and tier.

## What every database comes with

- **Branches**: Copy-on-write forks with their own credentials, ready in under a minute, reset to the parent in one call and deleted when their lifetime ends.
- **PostgreSQL 16 through 18**: Pick the major version at creation, 18 by default. CREATE EXTENSION works, including vector, uuid-ossp and pg\_trgm.
- **Autoscaling compute**: Six tiers from volcano-db-xs to volcano-db-2xl. Compute moves with load inside the tier.
- **Backups and restore**: On Superagent, restore manual or scheduled backups in place, or restore to a recent point in time without taking a backup first.
- **Row-level security**: auth.uid(), auth.email(), auth.role() and auth.is\_authenticated() are installed and ready to reference in policies.
- **Direct connections**: Port 5432 over TLS for psql, your ORM, or anything that speaks the Postgres wire protocol.

## Create it, protect it, branch it

### Migrate

```sql
create table if not exists notes (
  id          uuid primary key default gen_random_uuid(),
  user_id     uuid not null,
  title       text not null,
  archived    boolean not null default false,
  updated_at  timestamptz not null default now()
);

create index if not exists notes_user_updated on notes (user_id, updated_at desc);

alter table notes enable row level security;

-- The owner reads and writes their own rows. Nothing else does, including
-- code that forgets to add "where user_id = ...".
drop policy if exists notes_owner_read on notes;
create policy notes_owner_read on notes
  for select to authenticated using (user_id = auth.uid());

drop policy if exists notes_owner_write on notes;
create policy notes_owner_write on notes
  for insert to authenticated with check (user_id = auth.uid());

drop policy if exists notes_owner_update on notes;
create policy notes_owner_update on notes
  for update to authenticated
  using (user_id = auth.uid()) with check (user_id = auth.uid());
```

### Query

```typescript
import { VolcanoAuth } from '@volcano.dev/sdk';

const volcano = new VolcanoAuth({
  apiUrl: process.env.NEXT_PUBLIC_VOLCANO_API_URL!,
  anonKey: process.env.NEXT_PUBLIC_VOLCANO_ANON_KEY!,
});
volcano.database('app');

export async function loadNotes() {
  const { data, error } = await volcano
    .from('notes')
    .select('id, title, updated_at')
    .eq('archived', false)
    .order('updated_at', { ascending: false })
    .limit(20);

  // No user_id filter here. Row-level security already applied it, and a
  // client that tries to widen the query still gets its own rows.
  if (error) return [];
  return data;
}
```

### Branch

```bash
# Runs in CI on every push to a pull request.
set -euo pipefail

DB=app
BRANCH="pr_${PR_NUMBER}"

# The first push forks the branch. Later pushes rewind it to the parent's data.
volcano cloud databases branches create $DB $BRANCH --ttl 72h \
  || volcano cloud databases branches reset $DB $BRANCH --yes

for attempt in $(seq 1 60); do
  status=$(volcano cloud databases branches get $DB $BRANCH | awk '/^Status:/ {print $2}')
  [ "$status" = active ] && break
  [ "$status" = failed ] && exit 1
  if [ "$attempt" -eq 60 ]; then
    echo "Timed out waiting for database branch $BRANCH" >&2
    exit 1
  fi
  sleep 5
done

BRANCH_URL=$(volcano cloud databases branches get $DB $BRANCH --show-connection-string \
  | awk '/^Connection string:/ {print $3}')
[ -n "$BRANCH_URL" ] || { echo "Database branch has no connection string" >&2; exit 1; }

# The branch holds production's schema, so apply only this PR's new migrations.
git diff --name-only --diff-filter=A origin/main...HEAD -- 'volcano/migrations/*.sql' \
  | sort | while read -r f; do psql "$BRANCH_URL" -v ON_ERROR_STOP=1 -f "$f"; done
DATABASE_URL="$BRANCH_URL" npm test

# In the job that runs when the pull request closes. Left alone, the branch
# expires 72 hours after its last reset anyway.
volcano cloud databases branches delete $DB $BRANCH --yes
```

### Operate

```bash
# Inspect
volcano cloud databases list
volcano cloud databases get app --show-connection-string

# Migrations are .sql files under volcano/migrations, applied alphabetically.
# There is no tracking table, so write them to be safe on a second run.
volcano cloud databases migration up --all -d app

# See every branch, what it has changed, and when it expires
volcano cloud databases branches list app
volcano cloud databases branches extend app pr_482 --ttl 168h

# On Superagent, back it up and restore in place
volcano cloud databases backups create app before_004
volcano cloud databases backup-schedule set app --frequency daily --hour 3
volcano cloud databases restore app --backup before_004
volcano cloud databases restore app --to 2026-09-22T09:30:00Z
```

## What people build on it

### A database for every pull request

CI forks a branch when a pull request opens, runs the migrations and the test suite against real-shaped data, and deletes it when the pull request closes. Production never sees a migration until it merges, and a branch nobody cleaned up expires on its own.

### Retrieval for AI features

Embeddings in a pgvector column next to the source rows. One query filters by tenant, ranks by similarity, and obeys the same policies as everything else.

### A way back from a bad migration

On Superagent, take a backup before the risky change or restore to any moment in the seven-day window. The database comes back in place with the same connection string, so nothing holding it needs to change.

## Works with the rest of Volcano

- [Identity in your policies](https://volcano.dev/features/authentication): auth.uid() and auth.role() come from the session Volcano issued, so policies are written against real users.
- [Streams from your tables](https://volcano.dev/features/realtime): Subscribe to INSERT, UPDATE and DELETE on any table. The read policy filters the stream per subscriber.
- [Query from a function](https://volcano.dev/features/edge-functions): Store the connection string as the DATABASE\_URL project variable, then query as the caller on the event or as an admin.
- [Server component queries](https://volcano.dev/features/nextjs-hosting): The same client works in a server component, a route handler, or the browser.

## Frequently asked questions

### Is this real PostgreSQL?

Yes — PostgreSQL 16, 17 or 18, chosen when you create the database, with 18 as the default. It listens on 5432 over TLS, so psql, pg\_dump, Prisma, Sequelize, TypeORM and any driver that speaks the Postgres wire protocol connect to it normally. Extensions install with CREATE EXTENSION.

### Can I branch a database?

Yes. volcano cloud databases branches create makes a copy-on-write fork of the schema, rows, roles and policies, usually in under a minute, with its own connection string and password. A branch is charged only for the data it changes, lives between one hour and 30 days, and can be reset to the parent's current data at any time. Each database can have up to 10 branches on Hobby or 25 on Superagent.

### How can the browser query the database safely?

Because Postgres enforces the rules, not your code. Volcano establishes the caller's identity on the connection before the first statement runs, so auth.uid() returns the signed-in user and your policies filter every query — including one written by a client trying to read more than it should.

### How do migrations work?

As .sql files under volcano/migrations, applied in alphabetical order by volcano cloud databases migration up. There is no tracking table, so write migrations that are safe to run twice — or run your ORM's own migrations against the same connection string instead.

### Can I use pgvector?

Yes. Run CREATE EXTENSION IF NOT EXISTS vector in a migration and embeddings become a normal column you can index and join against.

### Are there backups?

Yes, on Superagent. Take a backup whenever you want one, set a daily, weekly or monthly schedule, and restore in place: the database keeps its connection string. You can also restore to any point in the seven-day restore window without having taken a backup at all. Hobby does not include backups or point-in-time restore.

## Get started

Ready to create your first database? Create a free account at https://volcano.dev/signup, or have your coding agent follow the setup playbook at https://volcano.dev/setup.md.
