Deploying Syntra
September 7, 2026 · View on GitHub
Syntra is a single self-contained binary plus a store directory. The binary
holds the HTTP server, the Lycan graph runtime, and the adaptive learning core;
the store directory holds everything the appliance learns. There are three
supported deployment shapes: a local Docker image for evaluation and
single-host production, a Helm chart for Kubernetes, and a bare-metal install
straight from cargo build. Pick the shape that matches your operational
posture; the running surface is identical across all three.
For the platform overview see ../README.md; for what
shipped in each phase see ../CHANGELOG.md. The API
endpoints referenced below are documented in api.md, and the
runtime concerns once it's up are in operating.md.
What you're standing up
Syntra serves a single HTTP listener on port 8787. There is no separate control plane, no database, and no message broker. State lives in the store directory under the layout below; the container or host is otherwise disposable.
syntra-store/
tenants/{tenant}/jobs/{job}/capsules/{capsule}/
current.lyc — installed graph binary
policy.json — runtime capability policy
memory.json — learned weights, meta-bandit, calibrators, OOD detectors
learning.json — algorithm config (contextSpec, refusal, …)
warmup.json — lifecycle state (Warmup / Active / Frozen)
audit.jsonl — mutation log
decision.jsonl — decision log (carries refused flag and confidence)
feedback.jsonl — feedback log
snapshots/ — pre-mutation backups
Backing up the appliance means backing up the store directory. Restoring it
means restoring the store directory and starting the binary against it. There
is no schema migration step — the memory.json reader is backward-compatible
across schema versions 2 through 7, so older stores load cleanly into newer
binaries.
Required configuration
The only mandatory setting is the admin key. Syntra refuses to start without
one unless you explicitly pass --dev-mode, in which case it binds to
127.0.0.1 only and prints a warning. Set it via environment variable:
LYCAN_ADMIN_KEY=<a long random secret>
The store root defaults to the working directory but should be set explicitly in any deployment that survives a restart:
LYCAN_STORE_ROOT=/var/lib/syntra
Generate a real key with openssl rand -hex 32 and feed it through whatever
secret-management story your environment uses; Syntra has no opinion about
where it comes from beyond requiring its presence. Failed bearer authentication
returns 401 and is logged with the remote address. Comparison is constant-
time, so brute-force attempts do not leak via timing.
Local Docker
The reference image is built from docker/Dockerfile.demo. It is a
multi-stage build: a Rust toolchain image compiles syntra from source against
the Lycan sources in the same checkout, and the runtime stage is a slim Debian
image carrying just the binary, the demo capsule, and a small traffic
generator. Build from the repository root:
docker build -t syntra:demo -f docker/Dockerfile.demo .
docker run --rm \
-p 8787:8787 -p 8080:8080 \
-e LYCAN_ADMIN_KEY=$(openssl rand -hex 32) \
-v syntra-store:/var/lib/syntra \
syntra:demo
Port 8787 is the API listener; port 8080 is the live dashboard included in
the demo image. The named volume syntra-store persists across container
restarts, image rebuilds, and upgrades — losing it means losing every learned
weight and the entire audit history, so back it up the same way you back up
any production database volume.
For a non-demo deployment, build a minimal image that runs syntra serve
without the dashboard or traffic generator. The demo image is the production
shape with two convenience processes added; strip them out for any environment
where the dashboard does not need to be exposed by Syntra itself (most
production deployments will fronted by an existing observability stack).
Kubernetes via Helm
A Helm chart lives at deploy/helm/syntra/ (see that directory if it
is present in your checkout). The chart deploys a single-replica Syntra
StatefulSet with a PersistentVolumeClaim for the store directory, a Service
exposing port 8787, and a Secret carrying LYCAN_ADMIN_KEY.
helm install syntra ./deploy/helm/syntra/ \
--set adminKey=$(openssl rand -hex 32) \
--set persistence.size=20Gi \
--set image.tag=latest
The single-replica posture is deliberate: the store is a local filesystem and the learner does not currently support multi-writer state, so scaling is vertical until a clustering mode lands. For HA today, run an active capsule with shadow-mode peers and promote on failure rather than running concurrent writers.
If the chart directory does not exist in your checkout, fall back to the bare-metal recipe below and wrap it in a manifest of your choosing.
Bare-metal
For Proxmox LXC, a systemd-managed VM, or any host where Rust is acceptable:
cd <repo root> # the repository checkout root
cargo build --release --bin syntra # add --bin lycan for the language CLI
install -m 0755 target/release/syntra /usr/local/bin/syntra
mkdir -p /var/lib/syntra
LYCAN_ADMIN_KEY=$(openssl rand -hex 32) \
syntra serve \
--addr 0.0.0.0:8787 \
--store /var/lib/syntra
Wrap this in a systemd unit, an LXC entrypoint, or whatever supervises long-running processes in your environment. For Proxmox LXC specifically, bind-mount the store from the host so the directory survives container rebuilds:
mp0: /mnt/data/syntra-store,mp=/var/lib/syntra
For a pre-built binary release (no Rust on the target host), build on a
build host, copy syntra and the appropriate libc-compatible glibc, and run
the same syntra serve command. The binary is self-contained at runtime; it
does not need a Lycan checkout once compiled.
TLS, proxies, and exposure posture
Syntra serves plain HTTP. Do not expose port 8787 to the public internet.
Run it behind a TLS-terminating reverse proxy — nginx, Caddy, Traefik, or
your cloud's load balancer — and lock the proxy down to your service network.
The threat model and the path to direct-exposure hardening live in
../SECURITY.md and are tracked in the Syntra issue
tracker; for now, treat the appliance as you would an internal datastore.
The proxy should forward Authorization headers untouched, preserve the
request body up to 4 MB, and keep the connection open long enough for the
slowest capsule on your installation to return a decision (default budget is
generous; capsules that call out via the HTTP capability are the slow path
to watch).
Network egress from the Syntra host should be restricted to whatever your
capsules explicitly need. Capsules with allow_network: false in policy
cannot reach out at all; capsules with allow_network: true are restricted
to their allowed_hosts list with SSRF protection against private ranges by
default.
Resource sizing
A single Syntra instance handles thousands of decisions per second on a
modest VM (2 vCPU, 2 GB RAM). The dominant memory cost is the in-memory
mirror of memory.json for active capsules; the dominant CPU cost is the
graph executor under high /decide rates. For most workloads the bottleneck
is feedback.jsonl fsync throughput, which is the limiting factor when you
run with snapshotOnFeedback: true and journalOnFeedback: true. Switch the
capsule's learning.json to "mode": "highThroughput" to disable those at
a small durability cost, or leave them on and put the store on faster local
storage.
Set CPU and memory limits on the container or unit. Syntra does not currently enforce its own resource ceilings; the supervisor is expected to.
Backups
The store is the entire backup target. cp -r of the store root, a volume
snapshot, or a restic backup against the store directory all produce a
restorable backup. Stop the appliance for a fully consistent snapshot, or
take a volume-level snapshot (LVM, ZFS, EBS) for a live backup with point-in-
time consistency.
Restore is symmetric: stop the appliance, replace the store, start the appliance against the restored path. There is no schema migration step.
A first-class HTTP backup endpoint is planned for Phase 1E; see
api.md for the current state.
Upgrades
Upgrade by replacing the binary or container image. The memory.json schema
reader is backward-compatible from version 2 through 7, so a newer Syntra
binary will read an older store without intervention. Roll forward by
shutting down the appliance, swapping the binary, and starting against the
same store. Take a backup first.
There is no documented downgrade path. If you must roll back, restore from a backup taken before the upgrade.