CapyDB Docs
← All posts

A database is a service unit

CapyDB is built around database cells: isolated Postgres runtimes that give each project its own storage, WAL, credentials, socket boundary, pooler, extensions, and CPU/RAM envelope.

July 8, 2026CapyDB team

The primitive

A database should be a service unit.

Not a backend platform. Not an application framework. Not a cloud console object that asks you to think about hosts, pools, ports, and maintenance scripts before you get to a connection string.

In CapyDB, that service unit is the database cell.

A cell is a managed Postgres runtime dedicated to one project. It has its own postmaster, data directory, WAL path, Unix user, socket boundary, credentials, PgBouncer pooler, extensions, and CPU/RAM envelope. CapyDB manages the lifecycle around the cell. Developers use normal Postgres URLs.

That is the architecture:

The useful part is not that CapyDB renamed a database. The useful part is that the platform is built around a smaller unit than a general database server and a simpler unit than a whole backend platform.

What a cell is

A CapyDB cell is one project-shaped Postgres runtime.

It contains the pieces that need to move together:

The public product can call this a database. The implementation treats it as a runtime boundary.

That distinction matters. A logical database inside Postgres is a namespace. A CapyDB cell is a process, storage, identity, network, and lifecycle unit.

The control plane creates cells

When a project is created, CapyDB does not just insert a row called project.

The control plane chooses a placement region, selects a pool with capacity, allocates an instance id and Unix uid, generates the database and role names, encrypts the project password, and enqueues an instance.create job. The worker claims that job and runs the host-side provisioning path.

The project row and instance row are created in one transaction. The project points at its primary cell, and the job points at the same cell. That gives CapyDB a durable control-plane model: every lifecycle action has an owning project, an owning organization, and a concrete runtime target.

The storage shape

Each cell gets three storage surfaces:

SurfacePurposeBoundary
heapPGDATA, tables, indexes, temp filesrefquota based on the project storage limit plus churn headroom
walpg_walseparate dataset with reservation for WAL safety
walspoollocal archive handoff before object storagebounded spool drained by the host archiver

This is not just a quota attached to a shared namespace. The data directory and WAL live in a layout built for cell lifecycle: create, resize, snapshot, branch, restore, and destroy.

That shape is what makes the database a manageable unit. A backup, import, restore, preview clone, plan resize, or credential rotation can target the cell instead of reasoning about an undifferentiated Postgres server.

The runtime boundary

The standard cell runs as its own systemd service:

capydb-pg@<cell-id>.service

The service runs Postgres under a per-cell Unix user:

capydb-<cell-id>

The standard posture is socket-only. The cell listens on its own Unix socket directory, not a public TCP port. The public connection path is handled by CapyDB's routing layer, which exposes stable direct and pooled Postgres URLs.

The customer sees standard Postgres. The platform sees a named runtime boundary it can operate.

The isolation model

The cell is not a VM. It is not a Kubernetes pod. It is not a marketing name for a database row.

It is a Postgres runtime isolated with boring Linux and Postgres primitives:

LayerCell boundary
Runtimeone Postgres postmaster per project
OS identityone Unix user per cell
Filesystemown ZFS heap, WAL, and walspool datasets
Socketown socket directory
Mount viewmount namespace hides sibling sockets
Networkstandard cells use loopback-only network namespace
CPUper-cell CPUQuota
Memoryper-cell MemoryHigh and MemoryMax
Credentialsgenerated project role and password
Extensionsenabled per project from the CapyDB allowlist
Poolingper-cell PgBouncer

That is the point of the primitive. The control plane can talk about one project database. The host can enforce one runtime boundary. The customer can use one normal connection string.

What CapyDB manages around the cell

Once the cell exists, the rest of the product is lifecycle management around that unit.

This is why CapyDB can stay narrow without being shallow. The product surface is small because the primitive is doing the work.

Extensions are part of the cell, not the platform

Extensions are managed at the project database level.

New cells start with CapyDB's default extensions enabled: vector, pgcrypto, uuid-ossp, and pg_trgm. Additional extensions are available through a curated allowlist. Enabling an extension queues a job, the host revalidates the allowlist, and the worker records the extension as enabled only after CREATE EXTENSION succeeds in the target database.

That keeps extension management inside the same model:

No arbitrary server mutation is exposed. No extension state is guessed from the dashboard. The database either has the extension or the job did not complete.

Versions are managed, not exposed as machinery

Each cell records its selected Postgres major (16, 17, or 18). Placement selects an active host that advertises that major; hosts install the supported majors side by side.

That is deliberate. Customers should not need to think about bindirs, package channels, or host-level Postgres installation details to create a database.

The right promise is:

CapyDB runs your project in a managed Postgres cell.

Not:

Pick any arbitrary Postgres build and operate it yourself.

The version is a cell property. The operation of that version is CapyDB's responsibility.

The API is the shape of the primitive

The API does not expose hosts and pools to normal users. Public placement is expressed as regions. Connections are expressed as normal Postgres URLs. Jobs are expressed as database operations.

The internal vocabulary is still there because the system has to operate real machines. But the customer-facing surface is the cell lifecycle:

User actionWhat CapyDB targets
Create projectprovision a new primary cell
Get connection stringreturn direct and pooled URLs for the cell
Create previewcreate or clone a preview cell
Reset previewreplace the preview cell
Import databaseload data into the project cell
Restore backuprestore into a preview or target cell
Rotate credentialsupdate the cell's project role password
Enable extensionrun extension job against the cell database
Delete projectdestroy the cell and credentials

That is the API to manage databases without asking the user to manage database infrastructure.

The boundary is explicit

Good primitives have edges.

A CapyDB cell is a strong runtime boundary, but it is not a claim of dedicated hardware. Standard cells share hosts and ZFS pools. CPU, memory, process, credential, socket, and storage limits are per cell. Storage IO on shared ZFS pools is still a shared physical resource. Heavier isolation belongs at the pool/device tier.

That boundary is not a weakness in the model. It is what makes the model honest and operable.

CapyDB is choosing a specific point in that design space: the database should behave like normal Postgres, but the managed unit should be small enough to isolate, branch, restore, resize, and delete directly.

Why this is the CapyDB architecture

Postgres is already the right database for a large class of applications. The missing piece is often not another database model. It is a cleaner service primitive around Postgres.

The cell is that primitive:

  • one project
  • one Postgres runtime
  • one storage boundary
  • one credential boundary
  • one socket boundary
  • one pooler
  • one lifecycle target

CapyDB can then build the rest of the product around that unit: previews, imports, backups, restores, extensions, connection pooling, integrations, alerts, and API keys.

The result is not "Postgres plus a dashboard." It is Postgres packaged as a managed service unit.

That unit is the database cell.