Products
Available

Loams Postgres

Serverless Postgres from Loams' hard fork of Neon, with storage in your bucket, copy-on-write branches and computes that start on demand and suspend when idle.

Loams Postgres is real Postgres with Neon's storage architecture. The Postgres process (the compute) keeps no durable state; the WAL goes to a quorum service; a pageserver turns it into layer files in your bucket and serves pages back. Because history lives in layers, a branch is a new timeline that starts at an LSN of its parent and copies nothing. Loams runs it from Loams Postgres, a hard fork of Neon (ostrium-labs/loams-postgres), and supplies the control plane Neon never open-sourced.

Use it when you want Postgres, not a Postgres-like engine: your extensions, your drivers and your ORM's migrations behave as they do on Postgres 17, and you want branches for previews, tests or agents, and idle databases that cost bucket bytes rather than a running server. Keep production data elsewhere for now. The development storage stack runs one stock safekeeper, default credentials and fsync = off in its compute spec, there is no connection pooler or TLS in front of the computes, and restore, the waker and the production WAL service are still being built.

Status

Loams Postgres is Available in Loams 0.1.0, the first release (2026-10-18). The feature table in docs/release/preview.md says: "The compose stack runs its storage and computes; --profile postgres-control adds pg-control and pg-runner, which provision computes and endpoints". In detail:

PieceIn 0.1.0Notes
Storage stack (deploy/loams-postgres-dev, included in the release compose): storage broker, one pageserver, one safekeeper, computesAvailableImages pinned by digest in deploy/pins/loams-postgres-images.toml: the fork's own signed release lp-2026.10.1. The safekeeper's WAL is on a volume, and loams upgrade drains it before recreating it
pg-control (loams.postgres.v1) and pg-runner, --profile postgres-control, image loams-pg-controlAvailableNew in 0.1.0 as a published image (the profile was in the compose file of preview.2, with the image built locally)
Projects, branches at an LSN or a time, endpoints, roles, databases, suspend when idleAvailableThrough loams.postgres.v1
PgDog front door, TLS, wake on connect, restore, loams-walNot shippedPlanned PG2c to PG2e

One line in the release notes is behind the code: their known limitations still say the images are "pinned to Neon's last public build until the fork's own builds (NF1 Task 14)". NF1 Task 14 is done; the pin file names lp-2026.10.1, built from ostrium-labs/loams-postgres with the same Postgres revisions as Neon's last public build (16.9 and 17.5), which the two previews shipped.

This is not the engine's Postgres wire listener. The loams engine can serve its collections over the Postgres protocol (--pg-listen, the pgwire feature, plan PG1). That is read-only SQL over Loams' own data, through DataFusion. It never runs a Postgres process and nothing on this page applies to it.

Architecture

The write path. A commit is acknowledged at the quorum, before anything reaches the bucket. The pageserver turns WAL into layers in the bucket, and computes read pages from the pageserver, never from the bucket. The 0.1.0 stack runs one stock safekeeper (a quorum of one); the launch design is loams-wal.

The pieces, with their names in the fork and in Loams (design §28 §3, §46 §6):

  • Compute. compute_ctl and Postgres with Neon's extension and walproposer, image loams-postgres-compute-v17 (or -v16). It has a local file cache but no durable data. It gets its configuration (the spec) by calling the control plane.
  • WAL service. The compute's walproposer speaks Neon's safekeeper protocol (v3). In the 0.1.0 stack that is one stock Neon safekeeper, which writes WAL to its disk and offloads it to the bucket under safekeeper/. The production design replaces it with loams-wal (crate loams-safekeeper), described below.
  • Pageserver. It reads the WAL from the WAL service, keeps it as delta layers, periodically materialises image layers, uploads both to the bucket and answers GetPage@LSN requests. A storage broker carries the timeline positions between them.
  • Bucket. RustFS in the dev stack, or any S3-compatible store. It is the durable copy of every layer (see the bucket as the source of truth).
  • Control plane. pg-control, a role of the loams binary (crate loams-pg-control). It serves loams.postgres.v1, keeps the records, drives the pageserver's API through loams-postgres (a plain HTTP client of the fork's components, with no Neon code dependency), answers each compute's spec request, and starts and stops computes.

How it works

Tenants, timelines and ids

The resource model is org → namespace → project → branch → endpoint → compute (D701). A project is one Neon tenant; a branch is one timeline; an endpoint is a stable address with its settings; a compute is a disposable Postgres process behind it, replaced rather than restarted. Tenant and timeline ids are derived from the Loams ids with SHA-256, so a retried create lands on the same tenant and a rename changes nothing in storage. Roles and databases belong to a branch and are inherited by its children, because they are catalog state inside the timeline.

Durability and the write path

A commit is acknowledged when the WAL service's quorum holds the WAL. The pageserver is not on the commit path; it catches up and is the source for reads. Layers reach the bucket after that. This is the shape every serious database on object storage uses: design §58's research notes that Neon, TiDB X and others keep commits off S3 and use the bucket for history and branches.

The production design changes who holds the quorum. On 2026-10-08 the owner ruled "remove safekeepers": loams-wal is to be the only WAL service at launch (D714), with a hot tier plus a 250 ms group commit of .lwal segments to the bucket. Two stores sit behind one trait: a TiKV hot tier (one fenced TiKV transaction per append) and Arm A, walproposer's own Paxos over three loams-wal acceptors on local NVMe, with TiKV holding only votes, leases and metadata. A bake-off against absolute targets (for example commit-1 p99 of at most 10 ms and bulk of at least 100 MB/s per timeline) picks one, and the owner rules (Q652). On the laptop runs recorded in §46 §9.2, Arm A passes the commit gates and fails bulk; the TiKV store is behind on both. None of it is in 0.1.0; the released safekeeper is a dev stand-in.

Branches

A branch is a new timeline whose ancestor is the parent at an LSN. Creating it copies no pages; the branch writes its own delta layers and reads older pages from the parent's layers.

CreateBranch takes a parent and a branch point: the parent's head, an LSN, or a time the pageserver resolves to an LSN. The point must lie inside the parent's history retention (7 days by default in the design, D713), or the call answers lsn_out_of_retention. Underneath it is one pageserver call that creates a timeline with ancestor_timeline_id and ancestor_start_lsn. The cost does not depend on the database's size, which is what makes a branch per pull request, per test run or per agent workspace practical.

Two limits come with the model. Branches cannot be merged back: Postgres has no way to merge timelines (§28 non-goals). And DeleteBranch refuses a branch that has children.

Computes start on demand and suspend when idle

An endpoint keeps its address; computes come and go behind it. Suspending moves no data. In the self-hosted stack, pg-control asks pg-runner to start and stop computes.

An endpoint's states are idle → starting → running → suspending → suspended, plus failed and deleting (D707). pg-control's endpoint reconciler suspends a compute after suspend_timeout_s with no activity: 300 seconds by default, 0 to never suspend. A new start creates a new compute with a new id and a new spec behind the same endpoint address. Measured on compose (PG2 ruling R17.3, 50 cold starts with images cached on a shared 14-CPU host), StartEndpoint on a suspended endpoint to the first SELECT 1 took a p50 of 1,417 ms and a p99 of 6,594 ms.

In 0.1.0 a start is an API call (StartEndpoint). Waking on a client's connection needs PgDog's queue and pg-control's waker, which is PG2 Task 26. PgDog's queueing behaviour was checked first (R18.2): a client waits in PgDog's pool while the backend is down and is served once the compute listens.

Who may start containers

Starting computes means holding a container runtime's socket, and whatever holds that socket can do anything its owner can. So neither the engine nor pg-control gets it (PG2 rulings R-REL.1 to R-REL.6). A separate process, pg-runner (loams pg-runner), holds it:

  • it is on an internal compose network with no route out, which only pg-control joins, and it publishes no ports;
  • every call needs a 32-byte token it writes into a volume pg-control mounts read-only;
  • its API is seven typed calls, and it starts only the compute images pinned in the pin file, never a neon-archive copy, from compose files it renders itself: no mounts, no privileges, no host network;
  • it runs as uid 10001 with every capability dropped. Prefer a rootless Podman socket, so the runner can do at most what your user can.

A filtering Docker socket proxy was considered and rejected: it filters by API endpoint, so a proxy that lets compose create containers lets anything create a privileged one.

Key decisions and why

See the decisions page for the index.

DecisionWhat it saysWhy, as the log states it
D231Loams Postgres is a fork of NeonUpstream was dormant since August 2025 and its compute Postgres was 16.9 and 17.5
D232Loams' control plane is the primary control plane: it calls the storage APIs and serves the compute spec and the storage controller's hooksNeon's control plane was never open source; the fork's components call it
D800, D820Ostrium Labs owns the fork as a product; then a hard fork named Loams Postgres, with no merges from neondatabase/*neondatabase/neon has had no commit since 2026-08-31; a soft fork would wait on merges that never come
D822, D823Rebrand what is ours (repository, images, lp-YYYY.MM.N tags, crate loams-neon → loams-postgres); keep compatibility names (neon extension, neon.* GUCs, neon_superuser, on-disk formats)Stored names are never renamed in place
D236, D708PgDog is the front door, unmodified, as a separate process, one per Loams namespacePgDog is AGPL-3.0; it is never linked, vendored or patched
D701, D707The project/branch/endpoint/compute model; computes as disposable processes with a 300 s default suspend timeoutStable addresses across compute replacement; idempotent retries
D713Branches are copy-on-write timelines at an LSN or time inside retention; restore is branch-and-swap with a backup branchNeon's storage gives PITR as branching; restores stay reversible
D714loams-wal is the only WAL service at launch; stock safekeepers are removedThe owner's directive of 2026-10-08; it reverses D233, which made the switch conditional on a benchmark gate
D1057TiKV and loams-wal stay separate by roleTiKV pays a timestamp round trip, a Raft write and an apply per append: fine for records, wrong for a byte log

What was rejected. Neon for the showcase apps' own databases (D149, replaced by CloudNativePG in D230). Keeping a soft fork that follows upstream (D802 and D811, superseded by D820 and D821). Neon's own proxy and autoscaler VMs (NeonVM needs KVM nodes that many clusters lack, §46 §7.4). And for the WAL, a pure bucket log: the spike measured 22 ms p50 and 148 ms p99, an order of magnitude over safekeepers (D234).

The fork, in practice

Design §51 and the NF1 plan turn "we own it" into work:

  • Builds and supply chain. GitHub Actions in the fork build loams-postgres-storage and loams-postgres-compute-v<N> for amd64 and arm64, with SLSA provenance, an SPDX SBOM and a keyless cosign signature, verified in Loams' CI. inferno (CDDL) is built out of every binary, and the private REST broker dependency stays a stub.
  • One pin file. deploy/pins/loams-postgres-images.toml is the only place digests and supported majors are written; a script generates and checks every consumer.
  • Postgres minors. The fork now merges postgres/postgres minors itself. 16.15 and 17.11 are merged and gated; a format question in 17.11 (upstream's new visibility-map block references in heap WAL records) was ruled on: the fork keeps Neon's record format, with a test that guards it. They ship in lp-2026.11.0 (NF1 Task 17).
  • Majors. 17 is supported, 16 is maintenance only, 18 when its gate passes, 19 as a beta after upstream's GA (D813).

What is compatible

  • Postgres versions. 17.5 (default) and 16.9 in lp-2026.10.1, the same revisions as Neon's last public build, which the previews shipped. The API takes pg_version; 0 means the server's default, 17.
  • It is Postgres. The compute runs PostgreSQL with Neon's patches, so the wire protocol, SQL, catalog and client behaviour are Postgres 17's. Users get neon_superuser, not superuser, in the design, so COPY … PROGRAM, untrusted languages and server file access are not available.
  • Tested in this repository. CI's Postgres jobs drive the pinned storage and real computes (pg2-e2e.yml); pg-control's tests talk to computes with tokio-postgres 0.7, and the PgDog checks used psql 17. The driver and ORM matrix the design lists (libpq 17, psycopg 3, asyncpg, node-postgres, postgres.js, pgjdbc, Npgsql, pgx, sqlx; Prisma, Drizzle, SQLAlchemy, Django, Rails, Hibernate, TypeORM, Flyway) and pg_regress through PgDog are PG2 Tasks 29 and 55 and have not run.
  • Extensions. Whatever the pinned compute image carries today. The managed catalogue with licence checks (compute/loams-extensions.toml) is NF1 Tasks 20 and 21.
  • PgDog. v0.1.60, pinned by digest, AGPL-3.0, verified for queueing, SCRAM verifiers in users.toml and RELOAD (PG2 Task 18). It does not yet front the computes in any stack.

How it compares

Neon and the managed services below have run in production for years; Loams Postgres had its first release on 2026-10-18. The table is about design and what exists today.

Loams PostgresNeonSupabaseAmazon Aurora PostgreSQL and RDS
RelationshipA hard fork of Neon's open-source codeThe original, now a Databricks companyPostgres plus a platform (auth, storage, realtime, functions)AWS-managed Postgres
StoragePageserver layers in your bucket; WAL quorumThe same architecture, operated by NeonPostgres on a disk per projectAurora: AWS's distributed storage; RDS: block volumes
BranchingCopy-on-write timelines at an LSN or timeCopy-on-write branches, the same modelBranches are separate instances seeded from migrationsAurora fast clones; RDS snapshots and restores
Scale to zeroSuspend when idle; start by API today, wake on connect plannedYes, with its proxy waking computesFree-tier projects pause; not per-connectionAurora Serverless v2 scales capacity; RDS does not
AutoscalingDesigned with in-place pod resize (D712); not runningNeonVM-based autoscalingInstance sizesAurora Serverless v2
Pooling and front doorPgDog per namespace (planned)Its own proxy and poolerIts own poolerRDS Proxy
Control planeOpen source, Apache-2.0, in LoamsClosedMostly open sourceClosed
Where it runsYour machines and bucketNeon's cloudSupabase's cloud, or self-hostedAWS only
Maturity0.1.0, first releaseProductionProductionProduction

Where the alternatives are better today: Neon has the same storage design with years of operation, a proxy that wakes computes on connect, autoscaling, and point-in-time restore that works now. Supabase gives you a whole application backend around Postgres, which Loams Postgres does not try to be. Aurora and RDS are the conservative choice on AWS, with HA, backups and support that Loams 0.1.0 does not have. Loams Postgres' case: Neon's architecture with an open control plane, on your own infrastructure and your own bucket, maintained independently after Neon stopped developing in the open, and sharing an API, identity and console with the rest of Loams.

What is missing, and what is planned

From the PG2 plan (docs/plans/2026-10-08-pg2-postgres-production.md) and NF1:

  • Not served yet: RestoreBranch (PG2 Task 44), IssueConnectCredential (Task 25), GetConnectionInfo (Tasks 20 and 25) and UpgradeProject (Task 51) answer "not built" with the task that builds them.
  • PgDog in front (PG2c): the per-namespace deployments and the loams-gateway SNI listener (Task 22), TLS on both hops (Task 23), SCRAM roles end to end (Task 24), the waker for wake on connect (Task 26), health and read routing (Task 27). Until then clients connect to an endpoint's published port directly.
  • loams-wal as the WAL (PG2d, Tasks 30 to 43, launch-critical): the in-process interpreted sender, broker publication, bulk throughput, TLS, HA and fencing, offload and archive restore, nemesis runs, the store bake-off, and migrating dev data off the stock safekeeper.
  • Operations (PG2e): bucket versioning and the restore drill, metrics and alerts, quotas, the Helm chart and Kubernetes end to end, storage HA, a security review, the performance gate.
  • Single node and desktop (PG2f): loams dev --postgres and the desktop on the API.
  • Self-hosted stack limits today: pg-control keeps its records in a local file on one node; its link to pg-runner is plain text on its internal network until PG2 Task 23; the dev storage stack uses default credentials, no TLS and fsync = off in the compute spec.
  • Postgres 18 and 19 (NF1 Tasks 24 to 35) and the extension catalogue (Tasks 20 to 23).

Try it

The release stack includes the storage stack. Follow docs/release/preview.md to create .env, then start everything:

cd loams/deploy/release
docker compose up -d

With the control plane (deploy/release/README.md). Set CONTAINER_SOCKET and CONTAINER_SOCKET_GID in .env (rootless Podman: CONTAINER_SOCKET=$XDG_RUNTIME_DIR/podman/podman.sock written out, and CONTAINER_SOCKET_GID=0), mint an API key, and start pg-control and pg-runner:

echo '{"keys": []}' > secrets/pg-api-keys.json
out=$(docker compose run --rm --no-deps pg-control pg-api-key \
        --subject you@example.com --grant admin:namespace:default)
jq '{keys: [.entry]}' <<<"$out" > secrets/pg-api-keys.json
jq -r .api_key <<<"$out"        # shown only this once
docker compose --profile postgres-control up -d pg-control pg-runner

pg-control serves loams.postgres.v1 on the host's loopback interface, port 7680 (PG_CONTROL_PORT). With PG_CONTROL set to that address and KEY to the key printed above:

curl -sS -H "authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{"namespace": "default", "name": "app", "idempotency_key": "1"}' \
  "http://$PG_CONTROL/loams.postgres.v1.PostgresService/CreateProject"

Endpoints are published on PG_ENDPOINT_BIND (loopback by default), on ports 55500 to 55999. Before docker compose down of the stack, suspend or delete the endpoints (or docker compose -p loams-pg-<namespace> down), or the stack's network stays in use.

By hand, on the storage stack alone (deploy/loams-postgres-dev/README.md), playing the control plane's part with curl. The pageserver's management API is on host port 9898 and the compute on 55433; set PAGESERVER and PGHOST to the host they are published on:

docker compose up -d rustfs create-bucket storage_broker pageserver safekeeper1

# Create a tenant and its first timeline through the pageserver API.
export TENANT_ID=$(openssl rand -hex 16) TIMELINE_ID=$(openssl rand -hex 16)
curl -X PUT -H 'Content-Type: application/json' \
  -d '{"mode":"AttachedSingle","generation":1,"tenant_conf":{}}' \
  $PAGESERVER/v1/tenant/$TENANT_ID/location_config
curl -X POST -H 'Content-Type: application/json' \
  -d "{\"new_timeline_id\":\"$TIMELINE_ID\",\"pg_version\":17}" \
  $PAGESERVER/v1/tenant/$TENANT_ID/timeline/

docker compose up -d compute1
until pg_isready -q -h $PGHOST -p 55433; do sleep 1; done
PGPASSWORD=cloud_admin psql -h $PGHOST -p 55433 -U cloud_admin postgres

Branch it: a new timeline whose ancestor is the first. It takes tens of milliseconds and copies no data. Add "ancestor_start_lsn": "<lsn>" to branch from a point in the past.

export BRANCH_TIMELINE_ID=$(openssl rand -hex 16)
curl -X POST -H 'Content-Type: application/json' \
  -d "{\"new_timeline_id\":\"$BRANCH_TIMELINE_ID\",\"ancestor_timeline_id\":\"$TIMELINE_ID\"}" \
  $PAGESERVER/v1/tenant/$TENANT_ID/timeline/
docker compose --profile branch up -d compute2
until pg_isready -q -h $PGHOST -p 55434; do sleep 1; done
PGPASSWORD=cloud_admin psql -h $PGHOST -p 55434 -U cloud_admin postgres

Source

On this page