Docker
The tracked Compose stack starts Chorus, SurrealDB, and Garage. It binds public-facing ports to loopback, pins images, runs read-only containers with bounded resources, and refuses to render until every credential has been supplied explicitly.
Create deployment secrets
Section titled “Create deployment secrets”Create a gitignored .env next to docker-compose.yml with permissions limited to your
user. Each deployment must use fresh values:
umask 077mkdir -p .secretsopenssl rand -hex 32 > .secrets/garage_rpc_secretopenssl rand -hex 32 > .secrets/garage_admin_tokenopenssl rand 48 > .secrets/chorus_rate_limit_hmac_keyopenssl genpkey -algorithm ED25519 \ -out .secrets/chorus_approval_execution_signing_key.pem{ echo "SURREAL_PASS=$(openssl rand -hex 32)" echo "GARAGE_RPC_SECRET_FILE=.secrets/garage_rpc_secret" echo "GARAGE_ADMIN_TOKEN_FILE=.secrets/garage_admin_token" echo "CHORUS_RATE_LIMIT_HMAC_KEY_FILE=.secrets/chorus_rate_limit_hmac_key" echo "CHORUS_APPROVAL_EXECUTION_SIGNING_KEY_FILE=.secrets/chorus_approval_execution_signing_key.pem"} > .envRun the tracked preflight before creating containers. It resolves Compose, rejects missing,
short, placeholder, or Garage-incompatible credentials, validates Garage’s exact key shapes,
and requires all four mandatory mounted secret files to be regular, nonempty, and exactly
mode 0400 or 0600. The rate-limit HMAC key must contain 32–4096 opaque bytes after
terminal line endings are removed. The approval-execution key must be one unencrypted
Ed25519 private key in PKCS8 PEM form; preflight validates its envelope, private-key
structure, and public algorithm without printing or copying its private bytes. It also
validates the runtime shutdown bounds and requires the whole-second
CHORUS_STOP_GRACE_PERIOD to exceed the graceful plus force-abort budgets. Preflight
reports names and reasons, never secret contents.
Supply S3 credentials from the generated owner-only file described below before invoking preflight. A fresh deployment uses temporary shape-valid values only long enough to render and start Garage; those values are never imported.
The operator scripts use the Docker CLI’s Compose v2 plugin by default. Set
COMPOSE_BIN=/absolute/path/to/docker-compose to select a reviewed standalone Compose v2
binary; the scripts invoke that path directly and still use docker for core Engine operations.
Start and bootstrap
Section titled “Start and bootstrap”Garage needs a one-time layout, bucket, and key before Chorus can use artifact storage.
Garage v1.0.1 accepts an imported secret only as a positional CLI argument, so Chorus never
automates key import. Normal bootstrap is strictly verify-only: it reads and checks the exact
single-node layout, bucket, configured key/secret, and RWO permission, but never assigns or
applies a layout, creates a bucket or key, deletes a key, or grants or revokes permission. It
fails closed when any persisted state is absent or different.
For a fresh Garage volume, let Garage generate the key. The explicit generation path creates a uniquely named key and captures its output in an owner-only recovery file. It durably stages the canonical shell credentials, grants only that invocation’s new key, re-reads and verifies the exact key, secret, and RWO permission, and only then atomically publishes the no-clobber output file. Any failure before publication deletes only the freshly created key by ID.
umask 077export S3_ACCESS_KEY="GK$(openssl rand -hex 12)"export S3_SECRET_KEY="$(openssl rand -hex 32)"export GARAGE_CREDENTIALS_FILE="$PWD/.secrets/garage_s3.env"test ! -e "$GARAGE_CREDENTIALS_FILE"
./scripts/deployment-preflight.shdocker compose up -d surrealdb garageGARAGE_BOOTSTRAP_GENERATE_CREDENTIALS_FILE="$GARAGE_CREDENTIALS_FILE" \ ./scripts/garage-bootstrap.sh. "$GARAGE_CREDENTIALS_FILE"./scripts/deployment-preflight.shdocker compose up -d chorusdocker compose psThe first S3 pair exists only to satisfy the fail-closed Compose model before Garage is
running; the generation command never imports it. Sourcing the generated file replaces both
variables with Garage’s actual key. The selected output path must be absolute and absent;
its existing parent directory must be owned by the current user and not group- or world-writable.
Bootstrap creates the file as mode 0600, fsyncs its file and directory state, and never
overwrites it.
If key deletion cannot be confirmed after an interrupted or failed generation, bootstrap keeps
$GARAGE_CREDENTIALS_FILE.recovery (canonical shell form) or
$GARAGE_CREDENTIALS_FILE.recovery.raw (the durably recorded unique name plus Garage CLI output), prints the fresh key ID or unique
name, and refuses to retry. Do not delete these files blindly. Inspect the reported key in
Garage, delete that freshly generated key with garage key delete <key-id> --yes if it still
exists, and only after confirming Garage state remove the recovery sibling and rerun bootstrap.
Neither recovery file is committed configuration; both remain owner-only and gitignored.
Keep that gitignored file as the canonical credential source. In every later shell, source it before Compose. Running bootstrap without generation is optional and idempotently verifies the existing layout, key, secret, bucket, and permission without changing Garage:
. "$PWD/.secrets/garage_s3.env"./scripts/deployment-preflight.sh./scripts/garage-bootstrap.shdocker compose up -dChorus is reachable at http://127.0.0.1:3000; SurrealDB is reachable locally at
127.0.0.1:8000. Garage’s S3 and administration ports are internal-only. To expose
Chorus beyond the host, place an authenticated TLS reverse proxy in front of it and set
SIWE_PUBLIC_URL, embedded-MCP authority allowlists, and trusted proxy policy explicitly.
The pinned SurrealDB image receives its initial root user and password through SURREAL_USER
and SURREAL_PASS environment entries; the password is never placed in container command
arguments, and the disposable drills attest both the resolved model and the running container.
Use the first admin profile
Section titled “Use the first admin profile”Compose mounts the private chorus-home named volume at /home/bun/.chorus. Zero-config
therefore writes the first admin profile inside the Chorus container, not to the host
account’s ~/.chorus/config.yaml. Verify it in place without printing the API key:
docker compose exec -T chorus bun run packages/cli/src/index.ts whoamiTo use the source CLI on the host, run from the repository root, install the frozen dependencies, and copy the profile into a dedicated gitignored home. The target must not already exist:
bun install --frozen-lockfileumask 077export CHORUS_CLI_HOME="$PWD/.secrets/chorus-cli-home"export CHORUS_HOST_CONFIG="$CHORUS_CLI_HOME/.chorus/config.yaml"mkdir -p "$CHORUS_CLI_HOME/.chorus"test ! -e "$CHORUS_HOST_CONFIG"docker compose cp chorus:/home/bun/.chorus/config.yaml "$CHORUS_HOST_CONFIG"chmod 600 "$CHORUS_HOST_CONFIG"HOME="$PWD/.secrets/chorus-cli-home" bun run packages/cli/src/index.ts whoamiKeep using that command-scoped HOME for later host CLI calls. Do not copy over an
existing CLI home, echo the file, or place its API key in a process argument.
Optional federation v2 secret
Section titled “Optional federation v2 secret”Federation remains disabled in the base stack. To enable it, create a separate key without
adding its contents to the image or .env:
umask 077openssl rand 48 > .secrets/chorus_federation_keychmod 600 .secrets/chorus_federation_keycat >> .env <<'ENV'FEDERATION_ENABLED=trueCHORUS_FEDERATION_PUBLIC_ORIGIN=https://chorus.exampleCHORUS_FEDERATION_KEY_ENCRYPTION_KEY_HOST_FILE=.secrets/chorus_federation_keyENV./scripts/deployment-preflight.shdocker compose up -d chorusThe base Compose model mounts that host file at the fixed in-container path. While federation
is disabled it uses an inert /dev/null source; when enabled, preflight resolves the same
project .env as Compose and refuses to proceed unless the host file, key length, and public
origin are valid. Startup reads the mounted file before connecting to SurrealDB. Do not reuse
the rate-limit HMAC key.
The optional local reranker is profile-gated:
docker compose --profile reranker up -d rerankerHealth and shutdown
Section titled “Health and shutdown”SurrealDB readiness uses its bundled /surreal isready command (the distroless image does not
put that binary on the docker exec PATH). Chorus probes GET /health/ready with Bun’s built-in
fetch; neither healthcheck assumes curl exists in a slim image. The readiness route is 200
only after startup owns the listener and all resources, and becomes 503 as soon as drain starts.
SurrealDB receives a two-minute start grace because a new RocksDB volume can take more than one
minute to open on the supported Docker Desktop path without being stuck.
GET /health is liveness and does not mean the process accepts work. Compose waits for both
SurrealDB and Garage health before starting Chorus. Chorus has a ten-minute health
start grace because measured first-ledger adoption and production-sized migrations can make
valid progress for more than five minutes before the listener opens; after that grace, twelve failed
ten-second probes mark it unhealthy.
docker compose pscurl --fail http://127.0.0.1:3000/health/readydocker compose downThe first SIGTERM or SIGINT closes admission, stops schedulers, waits for accepted HTTP/MCP
work and streams, closes resources in reverse acquisition order, and disconnects SurrealDB.
The default total deadline is 30 seconds (CHORUS_SHUTDOWN_TIMEOUT_MS); a second signal or the
deadline starts a bounded force-abort pass (CHORUS_FORCE_ABORT_TIMEOUT_MS, one second by
default), logs unfinished component diagnostics, and exits nonzero. Compose grants Chorus 45
seconds by default. Set CHORUS_STOP_GRACE_PERIOD (for example 311s for the maximum runtime
budgets) when increasing either timeout; deployment preflight rejects an equal or shorter grace.
Named volumes retain database, object, and local Chorus bootstrap state. Use the
tracked compose-backup.sh and restore runbook before an
upgrade. docker compose down -v destroys that state and is not an ordinary shutdown
command.
Every service uses bounded local JSON logs (three 10 MiB files). The normal restart policy is
unless-stopped; the disposable restore drill overrides it to no, so an abruptly abandoned
drill is not resurrected by a later Docker daemon restart.
The pinned SurrealDB image runs as uid 65532, so its named volume is mounted at the
image-owned /home/nonroot directory. Garage and Chorus retain only CAP_DAC_READ_SEARCH
after dropping all capabilities; this is the minimum read-only exception needed for
owner-only file-backed Compose secrets whose host ownership is preserved by bind mounts.