CLI reference
The tendb CLI ships in the npm package @10play/tendb. It requires Node.js 20 or newer.
npm install -g @10play/tendbtendb --versionEvery command talks to the DBLab Engine API on your engine host — through the platform’s native tunnel (AWS SSM by default; IAP on GCP, Bastion on Azure, plain loopback locally — no open ports, no SSH keys), or directly with --api-url. Configuration comes from flags, TENDB_* environment variables, and tendb.json — see the configuration reference.
Global conventions
Section titled “Global conventions”Global flags
Section titled “Global flags”These flags work on every leaf command:
| Flag | Default | Meaning |
|---|---|---|
--env <name> | — | Select an environment block from tendb.json |
--platform <name> | aws | Platform adapter: aws, gcp, azure, or local |
--region <region> | — | AWS/GCP region |
--profile <profile> | — | AWS profile (loaded via the shared credentials file) |
--ssm-prefix <prefix> | /tendb | SSM parameter prefix |
--instance-id <id> | — | DBLab host instance id (skips the SSM instance-id lookup) |
--api-url <url> | — | Direct DBLab API URL — no AWS/SSM at all; requires a configured token |
--config <path> | — | Explicit path to tendb.json |
-o, --output <format> | table | Output format: table or json |
--quiet | off | Suppress progress output on stderr |
stdout vs. stderr
Section titled “stdout vs. stderr”The CLI keeps stdout clean for results and sends all progress chatter to stderr:
- Results — connection URIs, tables, JSON — go to stdout.
- Progress lines (dimmed) and warnings go to stderr.
--quietsilences progress but not warnings. -o jsonprints pretty-printed JSON to stdout.- Passwords are masked in progress lines, but result URIs on stdout contain the real password — mask them in CI logs (see
tendb ci).
Branch names
Section titled “Branch names”Every command that takes a branch name applies the same rules:
- A bare number
Nbecomespr-N(CI shorthand):tendb branches create 42createspr-42. - Names must match
[a-z0-9][a-z0-9-]*, max 63 characters — lowercase alphanumerics and dashes, starting with an alphanumeric. Anything else exits 2 withinvalid branch name.
The name does triple duty: it is the DBLab branch name, the clone id, and (with dashes turned into underscores) the Postgres role name inside the clone.
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
| 0 | Success (including ci delete of an absent branch or a down platform) |
| 1 | Generic/API error, unexpected errors |
| 2 | Usage error (bad name, bad flag value, missing token in direct mode, …) |
| 3 | Branch/clone not found |
| 4 | Timeout, or a clone entered a FATAL state |
| 5 | Missing dependency (session-manager-plugin, terraform, or psql not on PATH) |
| 10 | Platform down (the SSM instance-id or token parameter is missing) |
| 42 | Clone capacity exhausted (port pool full) |
Errors print as error: <message> plus an optional hint: line on stderr. psql, tunnel, and migrate propagate the child process’s exit code.
tendb init
Section titled “tendb init”Scaffold a Terraform deployment plus tendb.json into the current project — the npm-native way to stand the infrastructure up anywhere.
npx @10play/tendb init # interactivenpx @10play/tendb init --platform aws --yes \ --region us-east-1 --pg-version 16 \ --source-secret-arn arn:aws:secretsmanager:... # scriptedPrompts for the platform (aws / gcp / azure / local) and its required inputs, then writes:
tendb/(override with--dir) —main.tf,variables.tf,outputs.tf, a generatedterraform.tfvars, aREADME.mdof next steps, and a.gitignorefor state files. The local platform addsseed/seed.sqlandscripts/host-setup.sh. It is plain Terraform you own — edit freely.tendb.jsonat the project root — created, or merged into an existing one (your keys win; switching an existing config to a different platform needs--force).
Every prompt has a flag twin, so --yes makes init fully scriptable:
| Flag | Platform | Meaning |
|---|---|---|
--dir <path> | all | Deployment directory (default ./tendb) |
--name <name> | all | Resource-name prefix (default tendb) |
--size <size> | all | small | medium | large | xlarge (default small) |
--pg-version <major> | all | Postgres major of the source — must match or restore fails |
--ref <git-ref> | all | Pin the Terraform module sources to a tendb version |
--modules-source <path> | all | Use a local modules checkout instead of git (development) |
--source-secret-arn <arn> | aws | Secrets Manager ARN of the source URL (blank → fill in later) |
--region <region> | aws, gcp | Region (global flag) |
--project <id> / --zone <zone> | gcp | Project and zone |
--source-secret-id <id> | gcp | Secret Manager id of the source URL |
--location <loc> | azure | Azure location (default northeurope) |
--subscription-id <id> | azure | Subscription (blank → ARM_SUBSCRIPTION_ID) |
--ssh-public-key <path|key> | azure | VM admin key — a path is read, a literal is used as-is |
--source-secret-name <name> | azure | Key Vault secret name for the source URL |
--source-url <url> | local | Source Postgres URL (blank → seeded demo container) |
--state-dir <path> | local | Params dir (default ~/.tendb/local) |
--yes(or a non-TTY) accepts defaults and errors, listing any answer that has no default and no flag.- A non-empty
--diris refused without--force;--forceoverwrites only scaffold-owned files and never deletes (state files and your own files survive).
Exit codes: 0 scaffolded · 1 cancelled at a prompt · 2 usage (missing required flags under --yes, platform mismatch without --force, non-empty dir).
tendb up
Section titled “tendb up”Bring the scaffolded deployment up: preflight → terraform init + terraform apply → fold the stack’s cli_discovery output back into tendb.json.
tendb up # interactive terraform approvaltendb up --yes # -auto-approve- The deploy dir resolves from
--dir, elsedeployDirintendb.json, else./tendb. - Preflight (skippable with
--skip-preflight) catches what a clean apply would hide: the source secret must exist (Secrets Manager on AWS,gcloud secrets describeon GCP), and on the local platform it runsscripts/host-setup.sh(colima VM + zpool) and points terraform at the colima Docker socket. - On Azure the first apply is two-phase —
uptargets the Key Vault first, prompts for the source URL (stored viaaz keyvault secret set), then applies the rest. --no-initskipsterraform init; a re-run is a plain re-apply.--env <name>writes the discovery outputs into thatenvironmentsblock instead of the top level (the block must already exist).
Exit codes: 0 up and wired · 1 terraform/preflight failure (message + hint on stderr) · 2 no deployment found · 5 terraform not on PATH (with an install hint).
tendb down
Section titled “tendb down”terraform destroy the deployment. Asks for confirmation (or --yes); refuses without --yes when non-interactive. Keeps tendb.json, the state file, and (locally) the colima VM — colima delete reclaims that. --dir <path> overrides the deployment directory, same resolution as up.
tendb down --yesExit codes: 0 destroyed · 1 cancelled or terraform failure · 2 no deployment found, or non-interactive without --yes · 5 terraform not on PATH.
tendb branches
Section titled “tendb branches”Manage branch databases — DBLab branches plus their copy-on-write clones.
tendb branches create
Section titled “tendb branches create”tendb branches create <name> [--from <branch>] [--fresh]| Flag | Default | Meaning |
|---|---|---|
--from <branch> | main | Base branch — honored only when the branch does not already exist |
--fresh | off | Snapshot the streaming sync target first, so the branch is “main as of now” rather than “as of the last snapshot” |
Creation is idempotent: an existing healthy clone is reused as-is; a wedged clone (any state other than OK) is deleted and recreated. The command waits for the first pool snapshot to exist (up to snapshotTimeoutSeconds, default 900 s), creates the DBLab branch if absent, creates the clone, and waits for it to reach OK (up to cloneTimeoutSeconds, default 120 s).
tendb branches create my-featuretendb branches create 42 # creates pr-42tendb branches create rehearse --from staging --freshOutput: table mode prints branch <name> ready on stderr and the connection URI as the only stdout line. JSON mode prints { name, state, port, uri }.
Exit codes: 0 success; 2 bad name; 4 snapshot timeout or clone failure; 42 capacity exhausted; 10 platform down.
tendb branches list
Section titled “tendb branches list”tendb branches listFetches branches and clone status in parallel and joins them. The table shows BRANCH STATE PORT DATA STATE AT AGE and excludes main; a N clone(s) running summary goes to stderr.
JSON mode prints { branches, clones } with the raw engine objects — including main, unlike the table.
tendb branches get
Section titled “tendb branches get”tendb branches get <name>Looks up the branch’s clone. Table mode prints a one-row table plus the connection URI on stdout; JSON mode prints { name, state, port, createdAt, uri }. Exits 3 if the clone does not exist.
tendb branches delete
Section titled “tendb branches delete”tendb branches delete <name>Deletes the clone (tolerating “already gone”), polls up to 60 s for it to disappear, then deletes the branch. Exits 0 even when the branch never existed; exits 4 if the clone is still present after deletion. Table mode prints nothing on stdout; JSON mode prints { name, deleted: true }.
tendb branches reset
Section titled “tendb branches reset”tendb branches reset <name>Deletes and recreates the clone on its existing branch — a fresh copy-on-write from the branch snapshot, discarding all changes made on the branch. Exits 3 if the branch has no clone.
Output: branch <name> reset on stderr and the URI on stdout; JSON mode prints { name, state, port, uri }.
tendb psql
Section titled “tendb psql”tendb psql <name> [-- <psqlArgs...>]Opens an SSM port-forward to the branch’s Postgres port (on a random local port) and launches psql connected to it, fully interactive. Extra arguments after -- are passed through to psql — they are placed before the connection string, because psql ignores trailing options.
tendb psql pr-42tendb psql pr-42 -- -c 'select count(*) from users'The CLI exits with psql’s exit code. If psql is missing from PATH, it exits 5 with an install hint (brew install libpq or postgresql). Not available in direct --api-url mode (exits 2 — port forwarding needs a platform session).
tendb connection-string
Section titled “tendb connection-string”tendb connection-string <name> [--local]Prints the branch’s connection URI — and nothing else — on stdout, neonctl-style. It opens no tunnel itself. -o json has no effect on this command.
| Flag | Meaning |
|---|---|
--local | Rewrite the host to 127.0.0.1 and the port to the clone’s remote port, for use through an already-open tendb tunnel <name> |
tendb connection-string pr-42tendb connection-string pr-42 --local # pair with: tendb tunnel pr-42tendb tunnel
Section titled “tendb tunnel”tendb tunnel [name] [--port <port>] [-- <cmd...>]| Argument / flag | Default | Meaning |
|---|---|---|
[name] | — | Branch to forward; omit it to forward the DBLab API port (2345) instead |
--port <port> | same as the remote port | Local port |
[cmd...] | — | Command to run with DATABASE_URL exported (write it after --) |
Foreground mode (no command): prints forwarding localhost:<local> → <remote> (ctrl-c to stop) and, when a branch was named, a ready-to-copy DATABASE_URL=... line — both on stderr. Blocks until Ctrl-C (exit 0). If the SSM session dies on its own (session duration limits), the command exits 1 so scripts notice — respawning is your job.
Exec mode (command after --): opens the tunnel, runs the command with DATABASE_URL set to the tunnel-localized URI and stdio inherited, then exits with the command’s exit code.
tendb tunnel pr-42 # forward pr-42's Postgres porttendb tunnel pr-42 --port 5433 # pick the local porttendb tunnel pr-42 -- npm test # run tests against the branchtendb tunnel # DBLab API on localhost:2345tendb status
Section titled “tendb status”tendb statusChecks engine health, fetches /status, reads clone capacity from the SSM port-pool parameter, and (when a replication publisher is configured) queries the publisher’s replication slots — all in parallel. The table shows:
| Row | Content |
|---|---|
health | ok or UNREACHABLE |
engine | DBLab Engine version |
transport | ssm (i-…), iap, bastion, local, or direct |
sync mode / sync status | Retrieval mode and state |
last refresh / next refresh | Refresh timestamps |
data state at | Data-state timestamp of the first pool |
disk | X.XG used / Y.YG (Z.ZG free) |
clones | n / cap when the port-pool parameter exists, else bare n |
On streaming deployments a second table follows, one row per logical replication slot: replication slot | state | behind | wal retained. behind is WAL the subscriber hasn’t confirmed yet (confirmed_flush_lsn → head); wal retained is WAL the publisher keeps on disk for that slot (restart_lsn → head) — the number that grows without bound while a slot is stalled.
Running clone ids are listed on stderr. JSON mode prints { healthy, transport, instanceId, engineVersion, retrieving, pools, clonesUsed, cloneCapacity, replication } (cloneCapacity is null when unknown; replication is null when no publisher is configured, and each slot carries lagBytes and walRetainedBytes).
tendb migrate
Section titled “tendb migrate”tendb migrate [branch] [--scratch] [--from <branch>] [--keep] [--fresh] -- <cmd...>Runs a migration command with DATABASE_URL pointed at a branch, then exits with the command’s exit code. This is the migration face of the CI contract.
| Flag | Default | Meaning |
|---|---|---|
--scratch | off | Create an ephemeral branch (migrate-<timestamp>), run, then delete it — even on failure, so reruns start pristine |
--from <branch> | main | Base branch for --scratch |
--keep | off | Keep the scratch branch after the run |
--fresh | off | Snapshot the streaming sync target first — rehearse on data as of now |
Name a branch, or pass --scratch for an ephemeral one; either way, the command comes after --. Omitting both the branch and --scratch, or omitting the command, exits 2 with a usage error.
tendb migrate my-branch -- npx prisma migrate deploytendb migrate --scratch -- npx prisma migrate deploy # rehearse + clean uptendb migrate --scratch --keep --fresh -- ./migrate.sh # rehearse on fresh data, keep the evidenceOn platform sessions the branch URL is tunneled per call; in direct mode the clone URI is dialed as-is (assumes in-VPC reachability).
Output: table mode prints one stderr line — ok on <branch> in <ms>ms or failed (exit N) on <branch> in <ms>ms, plus — scratch branch removed when applicable. JSON mode prints { ok, exitCode, durationMs, branch, kept } on stdout.
tendb ci
Section titled “tendb ci”Script-friendly verbs with a strict machine contract, designed as a drop-in for branch-per-PR shell scripts (see CI previews):
- The connection URI is the last line on stdout; everything else goes to stderr.
- A bare number
Nmeans branchpr-N.
tendb ci ensure
Section titled “tendb ci ensure”tendb ci ensure <id>Ensures the branch and clone exist and are ready (same idempotent semantics as branches create, without --from/--fresh), then prints the URI on stdout. Exit codes: 0, 42 (capacity), 4 (timeout/FATAL), 10 (platform down), 1.
tendb ci url
Section titled “tendb ci url”tendb ci url <id>Prints the URI of an existing branch database. Exits 3 when it does not exist.
tendb ci delete
Section titled “tendb ci delete”tendb ci delete <id>Deletes the branch database. Exits 0 both when the branch is already gone and when the platform is down (DBLab host absent — nothing to delete on stderr) — so PR-close cleanup jobs never fail spuriously. Other errors still propagate.
Mask the URI in GitHub Actions logs:
URI=$(tendb ci ensure "$PR_NUMBER" | tail -1)echo "::add-mask::$URI"tendb checkup
Section titled “tendb checkup”tendb checkup [--strict]Runs the full health rule set: engine health, disk usage, clone capacity, data staleness (measured from the newest pool snapshot in streaming mode), logical replication health on both publisher and subscriber, and schema drift. Cron-able — see operations.
| Flag | Meaning |
|---|---|
--strict | Exit non-zero on warnings as well as criticals |
Finding codes: engine-unreachable, sync-alerts, data-stale, disk-usage, clone-capacity, replication-publisher, replication-subscriber, subscription-disabled, replication-errors, slot-inactive, replication-lag, replication-stale, schema-drift. Disk and capacity findings escalate from warning to critical; replication connectivity findings are critical.
Default thresholds:
| Threshold | Default |
|---|---|
dataStaleHours | 26 — drops automatically to 2 when streaming replication is configured (unless explicitly overridden) |
diskWarnRatio / diskCriticalRatio | 0.8 / 0.92 |
capacityWarnRatio | 0.8 |
replicationLagBytes | 52428800 (50 MiB) |
replicationStaleSeconds | 300 |
Output: health ok — no findings, or a severity code message table. JSON mode prints { ok, findings, measuredAt }. Exit: 1 if any critical finding (or, with --strict, any finding at all); else 0.
tendb snapshots
Section titled “tendb snapshots”Pool snapshots for streaming deployments. The engine host runs tendb-snapshotd, which CHECKPOINTs the sync target and takes an O(1) ZFS snapshot — seconds at any database size. The CLI drives it through two SSM parameters: <prefix>/snapshots/config (the schedule) and <prefix>/snapshots/request (a nonce meaning “snapshot now”). See data refresh.
tendb snapshots list
Section titled “tendb snapshots list”tendb snapshots listLists pool snapshots, newest first (branch-head snapshots are excluded). Table: ID DATA STATE AT. JSON mode prints the raw snapshot array.
tendb snapshots create
Section titled “tendb snapshots create”tendb snapshots createRequests a snapshot now and polls until a new pool snapshot appears (default timeout 90 s; on timeout, exit 4 with the hint to check systemctl status tendb-snapshotd on the engine host). Table mode prints snapshot <id> ready in X.Xs on stderr and the snapshot id on stdout; JSON mode prints the snapshot plus durationMs.
tendb snapshots config
Section titled “tendb snapshots config”tendb snapshots config [--interval-minutes <n>] [--retain <n>]| Flag | Constraint | Meaning |
|---|---|---|
--interval-minutes <n> | 0–10080 | Minutes between scheduled snapshots; 0 = manual only |
--retain <n> | 1–500 | Pool snapshots to keep (in-use ones are never pruned) |
With no flags, shows the current schedule (defaulting to {intervalMinutes: 0, retain: 24} when the parameter is absent). With any flag, merges with the current config and writes it — the on-host executor picks it up within seconds.
tendb snapshots config --interval-minutes 60 --retain 24tendb schema
Section titled “tendb schema”DDL never travels over logical replication, so the sync target’s schema drifts silently when you migrate the source. These commands surface and reconcile that drift, driven by the contract params <prefix>/schema/config and <prefix>/schema/sync-request. The same param-store requirement as snapshots applies.
tendb schema diff
Section titled “tendb schema diff”tendb schema diffFingerprints every public table on the publisher and the subscriber (an md5 over each table’s ordered column/type/nullability list) and reports three buckets:
- missing on sync target — tables that exist only on the publisher; the first replicated write to one of these pauses the stream
- only on sync target — orphaned tables that will collide if recreated upstream
- columns differ — tables whose column definitions diverge
Prints schema in sync when clean; JSON mode prints { missing, orphaned, mismatched, inSync }. Exits 1 when drifted, 0 when in sync — usable as a CI gate. Exits 2 with cannot compare schemas when either side did not answer.
tendb schema sync
Section titled “tendb schema sync”tendb schema syncDestructive full reconcile via the engine-host daemon: missing tables are created, orphaned tables are dropped, mismatched tables are rebuilt, the publication is refreshed, and error counters are reset. The CLI writes a sync-request nonce and polls the drift check every 3 s until clean (default timeout 120 s; on timeout, exit 4 with a hint to check journalctl -u tendb-snapshotd on the engine host).
Output: a single stderr progress line, schema in sync (X.Xs). This subcommand prints nothing on stdout, even with -o json.
tendb schema config
Section titled “tendb schema config”tendb schema config [--auto-sync <on|off>]Shows or sets auto-heal: with it on, the engine-host daemon fixes additive drift (new tables) on its own at roughly one-minute cadence. With no flag, shows the current setting (default off). Table output: auto-heal on|off; JSON: { autoSync }.
tendb console
Section titled “tendb console”tendb console [--port <port>] [--no-open]| Flag | Default | Meaning |
|---|---|---|
--port <port> | 4400 | Local port |
--no-open | opens | Do not open a browser |
Starts the tendb web console — a Neon-style dashboard served locally, bound to 127.0.0.1 only. The server proxies the DBLab API, injects the verification token server-side, runs SQL against clones through on-demand tunnels, and runs a 60 s alert loop with optional Slack notifications (webhook stored in SSM at <prefix>/alerts/slack-webhook). The token and your AWS credentials never reach the browser. With a streaming sync target configured, branch main is served live and read-only from the sync target.
Prints tendb console: http://localhost:<port> on stderr and blocks until Ctrl-C. See the console guide for what’s inside.
tendb ui
Section titled “tendb ui”tendb ui [--no-open]Opens the DBLab Engine’s own embedded UI through two fixed-port tunnels: UI on localhost:2346 and API on localhost:2345 (the UI’s browser code calls localhost:2345, so local ports must match the remote ones exactly). Blocks until Ctrl-C. Needs a tunneling platform session — in direct --api-url mode it exits 2, because the embedded UI is bound to the host’s loopback.
| Flag | Meaning |
|---|---|
--no-open | Do not open a browser (a failed auto-open prints the URL instead) |
The verification token you need to paste into the UI’s auth field is printed on stdout.
Common errors
Section titled “Common errors”| Message | Exit | What to do |
|---|---|---|
DBLab host not found (/tendb/instance-id missing — platform down?) | 10 | Bring the platform up (terraform apply) |
session-manager-plugin not found on PATH | 5 | Install the AWS Session Manager plugin (the hint shows brew/dpkg commands) |
psql not found on PATH | 5 | brew install libpq (or postgresql) |
cannot reach DBLab API at <url> | 1 | Is the tunnel/engine up? Try tendb status |
clone <name> not found | 3 | The branch has no clone — create it |
clone capacity exhausted | 42 | Delete an idle branch (tendb branches list) or grow the terraform clone_port_range (see the engine module) |
no snapshot after 15m | 4 | Check docker logs dblab_server and /var/log/dblab-init.log on the host |
direct mode (--api-url) needs a token | 2 | Set token in tendb.json or TENDB_TOKEN |
no new snapshot after 90s | 4 | Check systemctl status tendb-snapshotd on the engine host |
AWS SDK credential errors (expired SSO, missing ssm:StartSession permission) are not wrapped — they surface as unexpected error: <stack> with exit 1.