novadocs
Guides

Guides

Using the nova CLI

Create buckets and streams, append and read from the shell, and run the fleet's lifecycle verbs with the operator CLI

nova is the operator CLI: bucket and stream lifecycle, appends and reads from the shell, fleet health, drain and decommission, api-key credentials, and token minting. It is built entirely on the public Go SDK and the admin plane, so everything it does is something any caller could do. This page walks the tasks; the command reference has every flag.

Install and connect

Install the CLI
go install github.com/supabase/nova/cmd/nova@latest

Every verb that talks to the cluster needs an address (a running novad, a star) from --addr or NOVA_ADDR. All planes share that one port, and any star serves any stream: the SDK underneath follows leader redirects for you.

Connect to the compose stack
export NOVA_ADDR=localhost:7781
./nova health
SERVING  node=… location=az-1 version=dev leading=0

Two verbs run offline and need no address: token mint and credential generate.

Credentials and TLS

NeedFlagsEnv
A bearer token on both planes--tokenNOVA_TOKEN
Verify the server's certificate (turns TLS on)--tls-caNOVA_TLS_CA
Mutual TLS--tls-cert, --tls-key (plus --tls-ca)NOVA_TLS_CERT, NOVA_TLS_KEY
Admin-plane basic auth--admin-user, --admin-passwordNOVA_ADMIN_USER, NOVA_ADMIN_PASSWORD

Without any TLS material the CLI dials plaintext. When --admin-user is set, basic auth replaces the bearer on the admin plane only (health, fleet, node, credential create/list/revoke); the data-plane verbs keep the token. Which mode your cluster expects is the authentication guide's subject.

Address streams as nova://bucket/key

Every stream verb takes one positional argument in the nova://bucket/key form: the bucket is everything up to the first /, the key is everything after it, verbatim: a key may contain further slashes, and orders, orders/, and orders/eu are three unrelated keys. The CLI never parses the key.

Bucket verbs accept the name with or without the nova:// prefix. The stream ID that stream create prints is internal identity, useful for correlating log lines and object keys; no verb accepts it in place of an address.

Buckets

A bucket is the container every stream is created in, and it must exist first; Nova has no implicit buckets. See buckets for the model.

Create a bucket
./nova bucket create demo
demo
  location="" create-on-append=false create-on-read=false default-durability=standard created=2026-08-24T10:00:00Z

The flags set the defaults new streams start from and the bucket's create-on-first-use behavior:

A bucket whose streams appear on first append, defaulting to Express
./nova bucket create telemetry --create-on-append --default-class express

With --create-on-append, appending to a missing key creates the stream from the bucket's defaults; --create-on-read does the same for reads (the stream is created empty). --location stores an opaque label.

Inspect, list, delete
./nova bucket get demo
./nova bucket list --limit 50
./nova bucket delete demo   # refused while any stream lives in it

bucket list prints one name per line and a next: -cursor <name> line when there is another page.

Streams

Create a stream
./nova stream create nova://demo/events
nova://demo/events
  durability=standard producer=any retention-max-age=0s throughput=0 id=…

A flag you leave out is filled from the bucket's defaults at the server; a flag you set overrides it for this stream only:

Create with explicit settings
./nova stream create nova://demo/orders --class express --producer fenced --retention-max-age 168h

The address can also come from --bucket (default default) and --key (default: a generated UUID). stream create creates-or-joins the bucket by convention, so with no bucket flag it lands in a default bucket it makes if missing.

Change a stream after creation
./nova stream reconfigure nova://demo/orders --retention-max-age 0   # back to unbounded
./nova stream reconfigure nova://demo/orders --class express          # a live stream hands off to switch

stream reconfigure names only what changes: a flag left out stays as it is, a flag set to zero sets zero. Retention applies at once; class, producer policy, and throughput reach the stream through a handoff, so a live producer reconnects once.

Get, list, delete
./nova stream get nova://demo/events
./nova ls demo                        # the bucket's streams: address and class per line
./nova stream list --bucket demo --limit 200
./nova stream delete nova://demo/events

ls is the browsing door: no argument pages the buckets, a bucket name (with or without nova://) pages its streams. Give it a bucket/key path and it refuses; use stream get for one stream. Stream listings are always per bucket (stream list --bucket, default default); there is no listing across buckets. Both listings print next: <cursor> when there is another page; pass it back with --cursor.

Append from stdin

append reads stdin and appends one record per line, the line's bytes as the body:

Append three records
printf 'one\ntwo\nthree\n' | ./nova append nova://demo/events
appended 3 entries

Lines are pipelined through one producer session (staged without waiting for acknowledgements, coalesced into shared wire batches), so a large seq 1 100000 | nova append … is one session's work, not a hundred thousand round trips. --linger (default 5ms) is how long a batch waits to fill; 0 cuts immediately. The first failed acknowledgement stops staging; whatever was already admitted still resolves, the count printed is what was acknowledged, and the exit code is 1.

When the address is served through a Gateway (HTTP has no session protocol) and no fencing token is given, append degrades to sequential one-shot batches of at most 1000 records or 1 MiB each. Nothing retries, so an ambiguous outcome surfaces instead of duplicating; resolve it with check-tail and a read of the suffix.

Lines are records

A record is one line; the newline is stripped. Lines longer than 64 KiB fail the scan. For binary bodies or headers use the SDK or the HTTP API.

Read, tail, and follow

Read from a sequence number
./nova read nova://demo/events --from 4 --limit 10

Each record prints as seq_num, RFC 3339 timestamp, and body, tab-separated:

4	2026-08-24T10:01:12Z	five
5	2026-08-24T10:01:12Z	six

Without --from, read starts at the earliest record that still exists: a trimmed stream reads from its trim point instead of erroring on 0. --since <RFC3339> starts at the first record stamped at or after that time. --follow (-f) switches to a subscription and keeps printing as records commit; --limit is ignored then.

tail shows the last records, oldest first: the window [tail−n, tail), clamped to what retention still holds:

The last 20 records, then keep following
./nova tail -n 20 -f nova://demo/events

check-tail prints the committed tail (one past the last record) and the last record's time:

Where does the stream end?
./nova check-tail nova://demo/events
6	2026-08-24T10:01:12Z

Read semantics (positions, clamping, subscribe resume) are covered in reading.

Trim

Trim advances the retention watermark: below it history is gone at once and reclaimed in the background; sequence numbers never renumber. Exactly one of the two forms:

Trim by sequence number or by time
./nova stream trim nova://demo/events --entry 4
./nova stream trim nova://demo/events --at 2026-08-01T00:00:00Z

--entry drops everything below that sequence number; --at drops everything stamped before that time. See retention.

Fence

fence is the only installer of a stream's fencing token. Give a token to install or rotate it; give none to clear it. It answers the admission boundary (the tail at which the new token took effect), and by return every producer session presenting a stale token has been superseded.

Install, rotate, clear
./nova fence nova://demo/orders writer-a      # fenced "writer-a"	boundary 12
./nova fence nova://demo/orders writer-b      # rotates: writer-a's sessions are fenced out
./nova fence nova://demo/orders               # cleared	boundary 30

Producers present the token with append --fencing-token writer-b. On a fenced stream presenting is mandatory while a token is set; on an any stream token-less writers always pass. Details in concurrency.

Fleet and health

One star, then the whole fleet
./nova health
./nova fleet

health answers SERVING or DRAINING, the star's ID, location, version, and how many streams it currently leads; once a draining star leads nothing it adds drained: safe to decommission. fleet lists every live star from the registry with its location and advertised address; these are admin-plane verbs, so they need whatever credential the admin plane demands.

Drain and decommission

Draining empties a star of leadership: it refuses new leaderships and, after a grace, hands off the streams it leads to the rest of the fleet. node drain targets the star at --addr and echoes its health line; watch leading= reach zero:

Drain the star at --addr, or reverse it
./nova --addr novad-2:7780 node drain
./nova --addr novad-2:7780 node drain --stop

Decommission is the durable, irrevocable declaration that a StarID will never serve again; any live star can issue it. Replacement hardware always mints a fresh ID.

Declare a drained star gone forever
./nova node decommission --star 0f7e5b2c-…

--force bypasses the drained-target guard for a registered but hung star. --cool-down lets the star's settle schedule run on its own clocks instead of the default force-drain. The whole procedure (scale-in, upgrades, the controller's ladder) is in node lifecycle.

Credentials and tokens

Long-lived api-key credentials live in the cluster's registry and are exchanged for short-lived scoped tokens. The short form:

Create a credential, exchange it, use the token
./nova credential create --scope 'nova:append:demo:* nova:read:demo:*'
# prints the key id, the secret exactly once, and the scope

export NOVA_EXCHANGE_SECRET=
export NOVA_TOKEN=$(./nova token exchange --key-id ck_… --bucket demo)
./nova read nova://demo/events

credential generate mints the root pair offline and writes the boot seed novad converges at startup (a file via --seed-out, or a NOVAD_ROOT_CREDENTIAL line via --env); credential list pages key ids and grants, never secrets; credential revoke <key-id> makes exchanges refuse at once while already-minted tokens live out their TTL. token mint signs an HS256 JWT offline against the same shared secret file novad verifies with; --admin grants the nova:admin marker. Scope grammar, auth modes, and the exchange are the authentication guide's subject.

Exit codes

CodeMeaning
0The command succeeded
1The command ran and failed; the error prints as nova: … on stderr
2A flag, usage, or unknown-command error; --addr missing on a dialing verb

Source documents

  • cmd/novactl/: the command tree and verb implementations
  • client/address.go: stream addressing in the SDK

On this page