Skip to Content
Getting StartedInstall & Run

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

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 returns 200 with {"status":"ok", "version", "uptime_ms"} once the process is up. It is liveness only.
  • GET /v0/ready (alias /readyz) returns 200 {"status":"ready", "wal_replay_complete":true, "topics":N} only after WAL replay completes. During replay it returns 503 not_ready with a Retry-After header and a replay progress fraction; while draining for shutdown it returns 503 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_AUTH interlock, 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 curl commands.
  • Configuration — the complete environment variable set.
  • Security — auth, scopes, prefix allowlist, and TLS.
  • Recovery — snapshot load, WAL replay, and torn-tail truncation.
Last updated on