Docs
Deployment Topologies
Deploy Chio as an in-process library or a sidecar service, with the signing key mounted only in the kernel container.
Source
deploy/cloud-run/service.yaml, deploy/ecs/task-definition.json, deploy/azure/container-app.bicep, and deploy/sidecar/Dockerfile.In-Process vs. Sidecar
In-Process Library
Link chio-kernel directly into your binary. The kernel evaluates calls without IPC, and the signing key lives in the same process as agent code.
- No local IPC hop. The kernel and agent share a process, which avoids serialization and socket handling.
- Trust assumption: agent code is trusted. A compromised agent process can read the signing key and forge receipts. Choose this only when the agent code, dependencies, and runtime are inside your trust boundary.
- Protect the signing key. Load it from the deployment's secret backend rather than a checked-in file or build-time constant.
Sidecar Service
Run chio-sidecar in a separate container next to the agent. The agent talks to it over local HTTP at localhost:9090.
- Separate key mount. In the reference manifests, only the sidecar mounts the signing key. Keep that mount out of the agent container to prevent an agent-process compromise from reading the key.
- Independent scaling and updates. Roll the kernel forward without redeploying the agent, and vice versa. Useful when the kernel and the agent are owned by different teams.
- Additional local HTTP work. The sidecar adds serialization and a localhost HTTP request. Measure that cost for latency-sensitive workloads.
- Recommended for untrusted agent code. The sidecar pattern is the supported deployment model for shipping Chio alongside third-party agent runtimes.
Orchestration Patterns
Three reference manifests ship in deploy/:
| Platform | Manifest | Startup ordering | Health |
|---|---|---|---|
| Cloud Run | cloud-run/service.yaml | Knative container-dependencies | httpGet startup + liveness probes |
| ECS Fargate | ecs/task-definition.json | dependsOn condition HEALTHY | curl-based healthCheck |
| Azure Container Apps | azure/container-app.bicep | multi-container probe sequencing | startup + readiness probes |
Cloud Run Reference
The Cloud Run manifest uses the run.googleapis.com/container-dependencies annotation to delay the app container until the sidecar reports healthy. The sidecar declares the only containerPort, so external traffic routes through it; the app stays reachable only on localhost.
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: agent-tool-server
spec:
template:
metadata:
annotations:
run.googleapis.com/container-dependencies: '{"app":["chio-sidecar"]}'
spec:
containers:
- name: app
image: APP_IMAGE_PLACEHOLDER
env:
- name: CHIO_SIDECAR_URL
value: "http://localhost:9090"
- name: chio-sidecar
image: ghcr.io/backbay-labs/chio-sidecar:latest
ports:
- containerPort: 9090
args: ["api", "protect",
"--upstream", "http://127.0.0.1:8080",
"--spec", "/etc/chio/spec/openapi.yaml",
"--listen", "0.0.0.0:9090",
"--allow-ephemeral-receipts",
"--authority-seed-file", "/etc/chio/seed/authority.seed"]
env:
- name: CHIO_LOG_LEVEL
value: "info"
# Gates /metrics and the sidecar control endpoints.
- name: CHIO_SIDECAR_CONTROL_TOKEN
valueFrom:
secretKeyRef:
name: chio-sidecar-control-token
key: latest
volumeMounts:
- name: chio-openapi-spec
mountPath: /etc/chio/spec
- name: chio-authority-seed
mountPath: /etc/chio/seed
startupProbe:
httpGet:
path: /chio/health
port: 9090
volumes:
- name: chio-openapi-spec
secret:
secretName: chio-openapi-spec
- name: chio-authority-seed
secret:
secretName: chio-authority-seedECS Fargate Reference
ECS uses dependsOn with condition: HEALTHY to delay the app container, plus a healthCheck that curls the sidecar's health endpoint. Secrets land via secrets[] entries referencing AWS Secrets Manager ARNs.
{
"containerDefinitions": [
{
"name": "app",
"image": "APP_IMAGE_PLACEHOLDER",
"dependsOn": [
{ "containerName": "chio-sidecar", "condition": "HEALTHY" }
]
},
{
"name": "chio-sidecar",
"image": "ghcr.io/backbay-labs/chio-sidecar:latest",
"command": [
"api", "protect",
"--upstream", "http://127.0.0.1:8080",
"--spec", "/etc/chio/spec/openapi.yaml",
"--listen", "0.0.0.0:9090",
"--receipt-store", "/var/lib/chio/receipts.db",
"--authority-seed-file", "/etc/chio/seed/authority.seed"
],
"environment": [
{ "name": "CHIO_LOG_LEVEL", "value": "info" }
],
"secrets": [
{
"name": "CHIO_SIDECAR_CONTROL_TOKEN",
"valueFrom": "arn:aws:secretsmanager:us-east-1:ACCOUNT_ID:secret:chio/sidecar-control-token"
}
],
"mountPoints": [
{ "sourceVolume": "chio-config", "containerPath": "/etc/chio", "readOnly": true },
{ "sourceVolume": "chio-seed", "containerPath": "/etc/chio/seed", "readOnly": true },
{ "sourceVolume": "chio-receipts", "containerPath": "/var/lib/chio", "readOnly": false }
],
"healthCheck": {
"command": ["CMD", "/usr/bin/curl", "-fsS", "http://localhost:9090/chio/health"],
"interval": 10,
"timeout": 5,
"retries": 3,
"startPeriod": 15
},
"readonlyRootFilesystem": true,
"user": "65532:65532"
}
]
}Keep these two settings in the manifest: readonlyRootFilesystem: true and the non-root user: "65532:65532". The sidecar image runs as a least-privileged user; reverting these knobs to their defaults is a regression.
Azure Container Apps
Azure Container Apps uses a Bicep multi-container deployment with startup, readiness, and liveness probes. It uses the same flags: the sidecar runs chio api protect with the spec and authority-seed-file mounts. The OpenAPI spec is an Azure Files share; the signing seed is a Key Vault secret delivered as a mounted file; CHIO_LOG_LEVEL is the only environment variable set.
Configuration
CLI Flags
The shipped chio binary is configured with CLI flags, not environment variables. The reverse-proxy sidecar subcommand chio api protect takes its upstream, listen address, route/scope table, durable audit log, and signing seed from flags:
| Flag | Purpose |
|---|---|
--upstream <url> | Base URL of the protected upstream (localhost within the task) |
--listen <addr> | Bind address for the kernel ingress. Defaults to 127.0.0.1:9090; the manifests bind 0.0.0.0:9090 |
--spec <path> | OpenAPI document the kernel derives its route and scope table from: the operator-provided spec, not the upstream |
--receipt-store <path> | Path to the durable SQLite audit log; place it on a per-instance local disk with a single writer |
--allow-ephemeral-receipts | Boot with an in-memory audit log instead of --receipt-store; evidence is lost on restart, for platforms with no per-instance disk |
--authority-seed-file <path> | Path to the signing seed used to sign receipts, delivered as a mounted secret file |
--upstream-timeout-secs <secs> | Wall-clock ceiling on a single upstream hop, including reading the full response. Defaults to 20 seconds; raise it for upstreams with legitimately slow calls or large bounded responses |
For local development and SDK quickstarts there is a simpler entry point: chio start runs the same axum router with zero-config defaults. It listens on 127.0.0.1:9090, keeps durable receipts by default (--allow-ephemeral-receipts switches to an in-memory store), and mounts no upstream proxy, so its catch-all route returns a loud 502. Production deployments that need --upstream, --spec, and a persistent store stay on chio api protect.
Environment Variables
The reference manifests set only a handful of environment variables; the route table, receipts, and signing material all come from the flags and mounted files above.
| Variable | Purpose |
|---|---|
CHIO_LOG_LEVEL | Tracing log level (info / warn / error); the one env var every reference manifest sets |
CHIO_SIDECAR_CONTROL_TOKEN | Bearer token gating /metrics and the sidecar control endpoints. A remote scraper sends it as Authorization: Bearer; a loopback caller needs none |
CHIO_TRUSTED_ISSUER_KEY | Single trusted issuer public key (hex-encoded Ed25519) |
CHIO_TRUSTED_ISSUER_KEYS | Multiple trusted issuer keys, comma-separated |
Mounted Configuration
Two files are mounted into the sidecar container: the operator-provided OpenAPI spec (--spec /etc/chio/spec/openapi.yaml) (the kernel derives its route and scope table from this document, never from the upstream) and the signing seed (--authority-seed-file /etc/chio/seed/authority.seed). Cloud Run mounts both from Secret Manager-backed volumes; ECS mounts the spec from an EFS volume and the seed from an EFS access point; Azure backs the spec with an Azure Files share and the seed with a Key Vault secret. On ECS the durable receipt store is a third mount, a per-task volume at /var/lib/chio. The sidecar fails closed at startup if the spec or seed cannot be loaded; the orchestrator restarts the revision.
Secret Backends
Two secrets reach the sidecar, by two different paths. The signing seed is a file, mounted from a secret volume; the sidecar control token is an environment variable, injected from the platform secret store.
- GCP: the authority seed mounts from a Secret Manager secret (
chio-authority-seed) as/etc/chio/seed/authority.seed;CHIO_SIDECAR_CONTROL_TOKENcomes from avalueFrom.secretKeyRef. The service account binds toroles/secretmanager.secretAccessor. - AWS: the authority seed mounts from an EFS access point;
CHIO_SIDECAR_CONTROL_TOKENis asecrets[].valueFromSecrets Manager ARN. The task role grantssecretsmanager:GetSecretValue. - Azure: the authority seed is a Key Vault-backed secret volume and the OpenAPI spec is an Azure Files share. A user-assigned managed identity holds Key Vault read access.
Diagnosing a Deployment
When an instance comes up unhealthy, reach for chio doctor before reading logs by hand. It runs six ordered probes and derives its exit code from the worst severity observed:
| # | Probe | Checks |
|---|---|---|
| 1 | Toolchain | Installed toolchain version against the workspace MSRV / rust-toolchain.toml |
| 2 | OCI | Guard-registry reachability |
| 3 | Cosign | Guard-bundle signature freshness |
| 4 | OTEL | Exporter endpoint resolution |
| 5 | Kernel runtime | /metrics reachability, asserting the chio_kernel_dispatch_inflight gauge is present |
| 6 | chio.yaml | Schema validity |
Exit codes follow the worst severity: 0 for ok / info / warning, 1 for error, 2 for fatal, so a CI gate can branch on fatal versus error. chio doctor --fix runs idempotent repairs after the probes (destructive repairs are rejected), and --skip-network drops the probes that would otherwise reach the network, for CI sandboxes.
Scaling Considerations
Three constraints decide how Chio scales horizontally:
- Receipt store I/O is the first bottleneck. The durable receipt store is a single-writer SQLite database in WAL mode (the
chio-store-sqlitebackend), pointed at a local disk with--receipt-store. A single node's write ceiling is bounded by that one disk; measure it for your hardware:cargo bench -p chio-store-sqlite --bench store_receipt_write_throughputexercises the same benchmark CI gates on. Beyond that ceiling, shard the store per tenant, or front a client-server audit store so writes fan out past one file. - Session journal locks contend on shared journals. Session-aware guards take a per-session mutex. Multiple replicas handling the same session serialize at the journal. Solve by pinning a session to a replica via consistent hashing on session ID, or by routing session-aware traffic to a single sidecar tier.
- Horizontal scaling requires either an external receipt store or per-tenant sharding. Per-replica SQLite stores produce per-replica checkpoint chains. That is fine for audit but inconvenient for cross-replica queries. Pick one of: a single-writer client-server audit store, or strict per-tenant routing where one tenant's receipts only ever land in one replica's store.
Don't merge SQLite stores by hand
previous_checkpoint_sha256. Use the federated-evidence import path (or a single-writer sink) instead.Next Steps
- Performance & Tuning · throughput targets and the four bottlenecks per topology
- Failure & Recovery · how the sidecar fails when its dependencies degrade
- Protect an API · the
chio api protectsubcommand the sidecar manifests run - Rotate Keys, Revoke · operational workflow for the signing key the sidecar holds