Architecture
tendb is a thin layer of Terraform, AWS plumbing, and CLI ergonomics around DBLab Engine (Database Lab Engine, by Postgres.ai) — the open-source engine that does the actual heavy lifting of snapshotting and thin-cloning Postgres on ZFS. One EC2 host in your AWS account syncs from your source database and serves copy-on-write branch databases. There is no control plane, no tendb service, and no state anywhere except the host itself.
The engine host
Section titled “The engine host”The Terraform engine module builds a single EC2 instance (Ubuntu 24.04 — chosen because ZFS is one apt-get away) with two volumes: a small gp3 root volume and a dedicated gp3 data volume that becomes a ZFS pool. At boot, the host installs Docker and ZFS, creates the pool, pulls the source database URL from Secrets Manager, writes a DBLab Engine config, and starts the dblab_server container.
Three sets of ports matter, and all of them stay inside your VPC:
| Port | What | Reachable how |
|---|---|---|
2345 | DBLab Engine REST API | SSM port-forward (or direct TCP if you open the security group) |
2346 | DBLab embedded UI | Bound to 127.0.0.1 on the host — SSM tunnel only, via tendb ui |
6000–6009+ | Clone Postgres port pool | SSM port-forward per clone (or direct TCP if opened) |
Every branch database is a Postgres container listening on one port from the pool, so the pool width is the hard cap on concurrent branches. The default range depends on the module’s size preset (10 ports for small, up to 50 for xlarge); grow it with the clone_port_range variable. tendb status reads the pool width from SSM and shows it as clones n / cap.
ZFS copy-on-write thin clones
Section titled “ZFS copy-on-write thin clones”The restored source data lives in a ZFS dataset. Creating a branch takes a ZFS snapshot-and-clone of that dataset and starts a fresh Postgres container on top of it. Copy-on-write means:
- Branch creation takes seconds, regardless of database size. Nothing is copied — the clone initially shares every block with its parent snapshot.
- A new branch consumes near-zero disk. The pool only grows by the blocks a branch actually writes (its “CoW delta”). Ten branches of a 100 GB database do not cost 1 TB; they cost 100 GB plus whatever each branch changes.
- Resetting a branch is cheap.
tendb branches resetthrows the clone away and re-clones from the branch’s snapshot — again in seconds.
Each clone is a full, writable, isolated Postgres. Writes on one branch never affect another branch or the source data.
Sync from source: logical dump/restore
Section titled “Sync from source: logical dump/restore”tendb does not replicate from your source database by default. On a schedule (refresh_cron, default 0 2 * * * — nightly at 02:00), the host runs a full logical refresh: pg_dump of the app database over the network into the ZFS pool, then pg_restore into the pool’s data directory, then a fresh snapshot.
Consequences of the dump/restore model:
- It works against any Postgres you can dial with a URL — Neon, Aurora, RDS, self-hosted. No replication slots, no superuser, no logical decoding permissions on the source.
- Branches are point-in-time copies “as of the last refresh,” not live followers.
tendb branches listshows each branch’sDATA STATE ATtimestamp. - A refresh is a full dump plus a full restore, so the data volume holds roughly the dump and the restored data (budget ~2.5× the source size).
- DBLab Engine skips a scheduled refresh non-destructively while clones exist, so a long-lived branch never gets yanked out from under you.
For sources where nightly staleness is not acceptable, the engine module also supports a streaming mode (streaming_snapshots = true) that keeps a live sync target on the host and takes O(1) ZFS snapshots of it instead of dump/restore cycles. See Data refresh for both modes in detail.
Discovery: the SSM parameter contract
Section titled “Discovery: the SSM parameter contract”The CLI has no configuration ceremony because the Terraform module and the host publish everything a client needs as SSM parameters under a prefix (default /tendb). This is the discovery contract:
| Parameter | Type | Published by | Content |
|---|---|---|---|
<prefix>/instance-id | String | Terraform | EC2 instance id of the engine host. Its absence means “platform down” — the CLI exits 10 and tendb ci delete treats it as “nothing to delete”. |
<prefix>/host | String | Terraform | The host’s private IP. |
<prefix>/verification-token | SecureString | Terraform (write-only value) | DBLab API token; also the input to clone password derivation. |
<prefix>/dbname | String | The host, at boot | The source database name parsed from the source URL. Terraform creates the parameter as a placeholder and ignores value changes, so terraform destroy still cleans it up. |
<prefix>/port-pool | String | Terraform | The clone port range as "6000-6009" — clients derive clone capacity from its width. |
A second group of parameters under the same prefix drives optional features; these are written by clients (the CLI, the console, or your Terraform) rather than being part of boot:
| Parameter | Written by | Purpose |
|---|---|---|
<prefix>/snapshots/config, <prefix>/snapshots/request | tendb snapshots / console | Snapshot schedule and “snapshot now” nonce (streaming mode). |
<prefix>/schema/config, <prefix>/schema/sync-request | tendb schema / console | Schema auto-heal flag and “full sync now” nonce (streaming mode). |
<prefix>/replication/publisher-url, <prefix>/replication/subscriber-url | operator | Replication endpoints for sync status and tendb checkup. |
<prefix>/alerts/slack-webhook, <prefix>/console-url | console | Slack alerting for the console. |
Transport: SSM Session Manager port-forwards
Section titled “Transport: SSM Session Manager port-forwards”The host accepts no inbound connections — by default its security group has zero ingress rules. Every byte between a client and the host travels through AWS Systems Manager Session Manager:
- The CLI reads
<prefix>/instance-idand callsStartSessionwith the AWS-managed documentAWS-StartPortForwardingSession, targeting the host and a remote port (2345 for the API, a pool port for a clone). - It spawns the local
session-manager-pluginbinary (the same one the AWS CLI uses), which holds the WebSocket to AWS and listens on a local port. - The CLI polls the local port until it accepts TCP, then talks plain Postgres or HTTP through it.
One API tunnel (to 2345) opens per CLI session; clone tunnels open on demand — tendb psql, tendb migrate, and tendb tunnel each forward the specific clone port they need. Authorization is pure IAM: ssm:StartSession on the instance (tag-conditioned) and on the port-forwarding document. No VPN, no bastion, no security-group changes.
Direct TCP is available as an opt-in for in-VPC clients (CI runners in the same VPC, the hosted console): the module’s allowed_security_group_ids / allowed_cidr_blocks variables open 2345 and the clone port range, and the CLI’s --api-url mode skips AWS entirely.
The stateless CLI
Section titled “The stateless CLI”tendb keeps no state — no local database, no session files, no server-side registry of branches. Every command reconstructs the world from two sources: the SSM parameters above and the DBLab Engine API. That design shows up everywhere:
- Deterministic credentials. A clone’s Postgres password is derived as
sha256("<token>:<branch>")truncated to 32 hex characters, and its username is the branch name with dashes turned to underscores. Any machine with IAM access to read the token can compute the connection string for any branch — nothing to look up, nothing to store. (See Security for the implications.) - Idempotent workflows.
tendb branches createis create-or-reuse: an already-running clone short-circuits, a wedged one is deleted and recreated.tendb ci deleteexits 0 when the branch — or the whole platform — is already gone. - Interchangeable clients. The CLI, the SDK, the console, and CI all speak the same contract, so a branch created in CI is immediately visible and connectable from a laptop.
tendb.jsonis configuration, not state. It only holds pointers (SSM prefix, region, profile, environments) and can be reconstructed from scratch.
Names are load-bearing in this model: a branch name is simultaneously the DBLab branch name, the clone id, and (dash→underscore) the Postgres role name, so names must match [a-z0-9][a-z0-9-]* (max 63 chars). A bare number is CI shorthand: tendb branches create 42 creates pr-42.
Where to go next
Section titled “Where to go next”- Security — the IAM, token, and secret model in depth.
- Data refresh — dump/restore vs. streaming snapshots.
- Terraform engine module — every input and output.
- CLI reference — all commands and exit codes.