Install & Run
topics ships as a single static binary or a multi-arch Docker image — no external
services, no runtime dependencies. This page covers building and running it, the default
loopback bind and dev-mode auth, the /v0/ready readiness gate during WAL replay, and
graceful shutdown. For the complete environment variable set see
Configuration; for hardening a real deployment see
Security.
The defaults are tuned for a safe local run: the server binds loopback
(127.0.0.1:4000) and disables auth when no keys are configured. That same default makes
it refuse to start on a non-loopback bind with no keys — so you can’t accidentally
stand up an open, unauthenticated event store.
Run it
Binary
Building from source needs a Rust toolchain. The release build is a single self-contained
binary named topics.
# Build the single binary.
cargo build --release
# Run it. Defaults: bind 127.0.0.1:4000, auth disabled (dev mode),
# WAL + segments + snapshots under ./topics-data.
./target/release/topics
# Listening on 127.0.0.1:4000 — auth disabled (dev mode).On start the server opens the data directory, loads the latest snapshot, and replays the
WAL forward before serving (see the readiness gate below). The
data directory defaults to ./topics-data; a missing or empty directory is a fresh start.
# Confirm it is up.
curl -fsS http://127.0.0.1:4000/v0/health # → 200 {"status":"ok", ...}First run, end to end
Start the server
Pick the binary or Docker path above. With no TOPICS_API_KEYS, a loopback bind comes up
in dev mode with auth disabled.
Wait for ready
If you have prior durable state, the server replays its WAL before serving. Poll
/v0/ready until it returns 200 (details below).
curl -fsS http://127.0.0.1:4000/v0/ready
# → 200 {"status":"ready", "wal_replay_complete":true, "topics":0}Write a record
A topic is created lazily on first write — no setup needed.
curl -X POST $TOPICS/v0/topics/orders \
-H 'content-type: application/json' \
-d '{ "records": [ { "data": { "sku": "AEROPRESS-GO", "qty": 1, "total": 3499 } } ] }'{ "topic": "orders", "first_seq": 1, "last_seq": 1, "seqs": [1],
"head_seq": 1, "count": 1, "created": true, "deduped": false,
"performance": { "server_total_ms": 0.62 } }From here, the Quickstart walks through reading from a cursor, watching over SSE, fanning out with a router, and deleting.
The readiness gate
topics separates liveness from readiness so an orchestrator never routes traffic to a process that is still recovering.
GET /v0/health(alias/healthz) always returns200with{"status":"ok", "version", "uptime_ms"}once the process is up. It is liveness only.GET /v0/ready(alias/readyz) returns200{"status":"ready", "wal_replay_complete":true, "topics":N}only after WAL replay completes. During replay it returns503 not_readywith aRetry-Afterheader and a replay progress fraction; while draining for shutdown it returns503 shutting_down.
# GET /v0/ready during WAL replay → HTTP 503
{ "error": { "code": "not_ready",
"detail": { "replay_progress": 0.42 } } }Wire /v0/ready to your load balancer / orchestrator readiness probe, not
/v0/health. Replaying a large WAL takes time (roughly sub-second per million records on
NVMe, worst case with no snapshot), and you do not want writes hitting a half-recovered
process. An acknowledged durable write is always recovered by replay — the gate just makes
you wait for it.
By default the probe endpoints (/v0/health, /v0/ready, /v0/metrics) skip auth so
they work behind a probe with no credentials. Set TOPICS_PROBE_AUTH=true to require auth
on them too.
Graceful shutdown
On SIGINT or SIGTERM the server shuts down gracefully: it stops accepting new work,
drains in-flight requests (returning 503 shutting_down from /v0/ready while it does),
and writes a final snapshot so the next start begins from a current checkpoint with
minimal WAL to replay. A clean stop and restart therefore comes back ready quickly.
Even a hard crash is safe by design: recovery loads the last snapshot, replays the WAL forward, and truncates any torn tail (a frame whose length runs past EOF or whose checksum fails to verify). A complete, checksum-valid frame for an acknowledged durable write is never lost. See Recovery for the full sequence.
Configuration & production
Everything above runs on defaults. To go further:
- All environment variables — bind host/port, data and cold-tier directories, body and segment size limits, hot-retention bounds, and every resource cap — are documented in Configuration.
- Production hardening — API keys (hashed at rest, with optional per-key scopes and a
topic-name prefix allowlist), the loopback/
TOPICS_ALLOW_INSECURE_NO_AUTHinterlock, TLS via a reverse proxy, and the DoS resource limits — is covered in Security.
topics speaks plain HTTP — it does not terminate TLS. For any non-loopback exposure, run it behind a TLS-terminating reverse proxy (or bind loopback and tunnel). See Security.
See also
- Quickstart — write, read, watch, fan out, and delete in a
few
curlcommands. - Configuration — the complete environment variable set.
- Security — auth, scopes, prefix allowlist, and TLS.
- Recovery — snapshot load, WAL replay, and torn-tail truncation.