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.
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:
| Surface | Purpose | Boundary |
|---|---|---|
heap | PGDATA, tables, indexes, temp files | refquota based on the project storage limit plus churn headroom |
wal | pg_wal | separate dataset with reservation for WAL safety |
walspool | local archive handoff before object storage | bounded 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>.serviceThe 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:
| Layer | Cell boundary |
|---|---|
| Runtime | one Postgres postmaster per project |
| OS identity | one Unix user per cell |
| Filesystem | own ZFS heap, WAL, and walspool datasets |
| Socket | own socket directory |
| Mount view | mount namespace hides sibling sockets |
| Network | standard cells use loopback-only network namespace |
| CPU | per-cell CPUQuota |
| Memory | per-cell MemoryHigh and MemoryMax |
| Credentials | generated project role and password |
| Extensions | enabled per project from the CapyDB allowlist |
| Pooling | per-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 action | What CapyDB targets |
|---|---|
| Create project | provision a new primary cell |
| Get connection string | return direct and pooled URLs for the cell |
| Create preview | create or clone a preview cell |
| Reset preview | replace the preview cell |
| Import database | load data into the project cell |
| Restore backup | restore into a preview or target cell |
| Rotate credentials | update the cell's project role password |
| Enable extension | run extension job against the cell database |
| Delete project | destroy 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.