Skip to main content

exd-server

The HTTP service binary. Run as a long-lived daemon.

Synopsis

exd-server

exd-server takes no command-line flags. All configuration is via environment variables — see Configuration below.

Description

Serves the server API: tenants, flag namespaces, tokens, manifest push/pull, version history, server-side evaluation, git smart-HTTP, SSE event stream, and the closure-snapshot endpoint. Backed by SQLite for metadata, a per-flag-namespace bare git repo for manifest history, and a pluggable HTTP transport.

The binary is single-process, single-host. There is no clustering in v0; if you need horizontal scale, terminate at a load balancer and serve from a shared volume — but understand that SQLite serializes writes, so the design point is a single primary.

Use cases

  • Self-host the central flag-namespace repo. A single instance serves multiple tenants. CI pushes via exd manifest push (or git push); developer SDKs poll or open SSE connections for live updates.

  • Run alongside your service mesh. Mount behind your existing TLS terminator. exd-server itself listens plain HTTP — terminate TLS at the reverse proxy.

  • CI-only deployment. Some teams run exd-server inside CI as a transient instance for integration tests against a fixed manifest. Bootstrap, push, run tests, tear down.

Configuration

Environment variables

VariableDefaultDescription
EXD_DB_PATH./exd.sqliteSQLite database file. Created on first start; subsequent starts open in place.
EXD_LISTEN127.0.0.1:8080Listen address (host:port). Use 0.0.0.0:8080 to expose on every interface.
EXD_GIT_ROOT./exd-data/git (sibling of EXD_DB_PATH when set explicitly, else cwd-relative)Directory containing one bare git repo per flag namespace: ${EXD_GIT_ROOT}/<tenant>/<slug>.git. Must be writable.
EXD_GIT_HOOK_BINARYsibling of the running exd-server executablePath to the exd-server-git-hook pre-receive binary. Override only if the layout isn't standard.
RUST_LOGinfoTracing filter, standard tracing-subscriber::EnvFilter syntax. Useful values: info,exd_server=debug, warn, info,sqlx=warn.

Bootstrapping

exd-server will refuse to serve a database that has not been initialized. Before the first start, run:

exd-server-admin bootstrap --db /var/lib/exd/exd.sqlite

See exd-server-admin bootstrap for the full procedure.

Migrations

exd-server does NOT auto-migrate at start. After a binary upgrade, run:

exd-server-admin migrate --db /var/lib/exd/exd.sqlite

before starting the new binary. This is deliberate — automatic migrations couple deployment cadence to schema changes in ways that have bitten other projects.

TLS

exd-server listens plain HTTP. Terminate TLS at a reverse proxy (nginx, Caddy, Envoy). The token-bearing surface assumes a trusted TLS terminator in front; never expose EXD_LISTEN to the public internet directly.

Closure signing key

The HMAC key that signs the short-lived ?token=… URLs in SSE closure_url payloads is auto-minted at bootstrap and stored in the database (server_secrets table). There is no environment-variable override; rotate by direct DB manipulation if compromised. The key is shared across all tenants on the instance.

Operating recipes

systemd unit

[Unit]
Description=exd-server
After=network.target

[Service]
ExecStart=/usr/local/bin/exd-server
Environment=EXD_DB_PATH=/var/lib/exd/exd.sqlite
Environment=EXD_LISTEN=127.0.0.1:8080
Environment=EXD_GIT_ROOT=/var/lib/exd/git
Environment=RUST_LOG=info,exd_server=info
User=exd
Group=exd
Restart=on-failure

[Install]
WantedBy=multi-user.target

Container

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates git && rm -rf /var/lib/apt/lists/*
COPY exd-server /usr/local/bin/
COPY exd-server-admin /usr/local/bin/
COPY exd-server-git-hook /usr/local/bin/
ENV EXD_DB_PATH=/data/exd.sqlite
ENV EXD_GIT_ROOT=/data/git
ENV EXD_LISTEN=0.0.0.0:8080
VOLUME /data
EXPOSE 8080
ENTRYPOINT ["exd-server"]

Run with --init so SIGTERM propagates correctly. Mount /data as a persistent volume.

Backups

The full state of the server is two artifacts:

  1. The SQLite database at $EXD_DB_PATH — back up with the standard sqlite3 .backup command (online) or a filesystem-level snapshot taken when no write is in flight.
  2. The $EXD_GIT_ROOT directory — back up with rsync or any directory backup tool. The bare repos contain the entire history of every flag namespace.

Restore both together. Restoring only one will leave the server in a partial state.

Body-size limits

  • JSON request bodies: 64 MB
  • Git smart-HTTP bodies: 200 MB

If you push very large manifests, expand the reverse proxy's body limit accordingly.

Logs and tracing

Stdout carries structured logs in tracing's default format. Set RUST_LOG=info,exd_server=debug for request-level traces during incident response. Tokens are never logged in cleartext.

Stopping cleanly

exd-server handles SIGTERM gracefully: it drains in-flight requests, finishes any in-progress git push, then exits 0. Allow at least 30 s in your supervisor's stop timeout.

Exit codes

CodeCondition
0Clean shutdown (SIGTERM).
non-zeroStartup failure (DB unreachable, port in use, missing bootstrap), or unrecoverable internal error. The log message gives the cause.

See also