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(orgit push); developer SDKs poll or open SSE connections for live updates. -
Run alongside your service mesh. Mount behind your existing TLS terminator.
exd-serveritself listens plain HTTP — terminate TLS at the reverse proxy. -
CI-only deployment. Some teams run
exd-serverinside CI as a transient instance for integration tests against a fixed manifest. Bootstrap, push, run tests, tear down.
Configuration
Environment variables
| Variable | Default | Description |
|---|---|---|
EXD_DB_PATH | ./exd.sqlite | SQLite database file. Created on first start; subsequent starts open in place. |
EXD_LISTEN | 127.0.0.1:8080 | Listen 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_BINARY | sibling of the running exd-server executable | Path to the exd-server-git-hook pre-receive binary. Override only if the layout isn't standard. |
RUST_LOG | info | Tracing 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:
- The SQLite database at
$EXD_DB_PATH— back up with the standardsqlite3 .backupcommand (online) or a filesystem-level snapshot taken when no write is in flight. - The
$EXD_GIT_ROOTdirectory — 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
| Code | Condition |
|---|---|
0 | Clean shutdown (SIGTERM). |
| non-zero | Startup failure (DB unreachable, port in use, missing bootstrap), or unrecoverable internal error. The log message gives the cause. |
See also
exd-server-admin— every operational task that doesn't go through HTTP.- Server API spec — the HTTP contract.
- User guide § Self-hosting
exd-server— end-to-end installation walkthrough. - Access-control spec — who can call which endpoint.