Container Images
Three reference Dockerfiles ship in the chio repository, one per deployment shape. The default image gives you the chio CLI plus a built-in trust-control demo. The sidecar image is the canonical production-grade kernel runtime. The TEE image runs the shadow- replay binary inside a confidential-compute envelope. All three share the same multi-stage build pattern: compile cargo build --release --locked in a Rust builder, then ship the binary on a minimal runtime base.
Source
Dockerfile, Dockerfile.sidecar, Dockerfile.tee, deploy/sidecar/Dockerfile, and .github/workflows/sidecar-image.yml.Image Overview
| Image | Dockerfile | Binary | Base | Exposed |
|---|---|---|---|---|
| Default kernel image | Dockerfile | chio | Alpine 3.22 | 8940 (trust-demo target), 8931 (mcp-demo target) |
| Sidecar HTTP service | Dockerfile.sidecar | chio | Alpine 3.22 + tini | 9090 (default CHIO_LISTEN_ADDR) |
| TEE shadow runner | Dockerfile.tee | chio-tee | Alpine 3.22 + tini | none (verdict-only by default) |
A fourth Dockerfile, deploy/sidecar/Dockerfile, is the distroless variant of the sidecar image used by the published GHCR artifact. It shares the same build pattern but ships on gcr.io/distroless/cc-debian12:nonroot (uid 65532) and bakes a multi-arch curl for the HEALTHCHECK probe. See Sidecar HTTP Service for that path.
Default Kernel Image
Dockerfile builds a single-stage Rust binary plus an optional dashboard SPA, packaged as three compose-friendly targets: the bare CLI, a trust-control demo, and an MCP-server demo. The dashboard stage builds the chio-cli/dashboard Vite SPA so the trust-demo target can serve it.
Build
# Build the bare CLI image (default target).
docker build -t chio:cli .
# Build the trust-control demo image (dashboard included).
docker build --target chio-trust-demo -t chio:trust-demo .
# Build the MCP-server demo image.
docker build --target chio-mcp-demo -t chio:mcp-demo .Stages
- rust-builder:
rust:1.93-alpine3.22withbuild-base,cmake,perl, andpkgconf. Buildscargo build --release --locked -p chio-cli --bin chio. - dashboard-builder:
node:22-alpine. Builds the Vite dashboard for the trust-demo target. - chio: Alpine 3.22 runtime with
ca-certificates,libgcc, andlibstdc++. DefaultCMDis--help. - chio-trust-demo: extends
chio, copies the dashboarddist/, exposes 8940, and startschio trust servewith a demo service token. - chio-mcp-demo: extends
chio, adds python3 and a mock MCP server, exposes 8931, runschio mcp serve-http.
Env vars (demo targets only)
| Variable | Default | Used by |
|---|---|---|
CHIO_SERVICE_TOKEN | demo-token | trust-demo |
CHIO_CONTROL_URL | http://chio-trust-demo:8940 | mcp-demo |
CHIO_CONTROL_TOKEN | demo-token | mcp-demo |
CHIO_AUTH_TOKEN | demo-token | mcp-demo |
Demo targets are for examples, not production
demo-token defaults so a bare docker compose up brings a working demo online. They are not safe to expose outside a development network. Production sidecars use Dockerfile.sidecar with secrets-backed env vars.Sidecar Image
Dockerfile.sidecar is the canonical sidecar build. Two stages: an Alpine Rust builder produces a stripped, musl-linked chio binary; a minimal Alpine runtime ships only that binary plus tini and CA roots. Runs as a non-root chio:chio user (uid/gid 10001) with CHIO_HOME=/var/lib/chio.
Build
docker build -f Dockerfile.sidecar -t chio-sidecar:local .Stages
- builder:
rust:1.93-alpine3.22(digest-pinned) withbuild-base,cmake,perl,pkgconf,musl-dev, andprotoc. Runscargo build --release --locked --package chio-cli --bin chio, strips the binary, copies it to/chio. - runtime:
alpine:3.22(digest-pinned) withca-certificatesandtini. Adds thechiouser, copies the binary to/usr/local/bin/chio, and setsENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/chio"].
Why protoc
chio-envoy-ext-authz, reachable transitively through the workspace, invokes tonic-build at compile time. CI installs protobuf-compiler for the same reason. The vendored Envoy protos consumed by tonic-build live under crates/chio-envoy-ext-authz/proto, so the existing COPY crates ./crates line covers them; no separate top-level COPY proto is required.
Default CMD
CMD ["--help"]. Both chio run and chio mcp serve-http require --policy plus positional input, so a bare docker run prints usage rather than crashing. Operators always override at deploy time.
Image size
Alpine + musl-linked binary keeps the runtime layer small. The runtime image carries only ca-certificates, tini, and the chio binary itself. Expect a compressed image around 20 to 40 MB depending on the build profile and feature set.
TEE Image
Dockerfile.tee mirrors the sidecar build pattern but builds the chio-tee binary, the confidential-compute shadow replay runner. It exposes no public port and runs in a verdict-only mode by default.
Build
docker build -f Dockerfile.tee -t chio-tee:local .Default env vars
| Variable | Default | Purpose |
|---|---|---|
CHIO_HOME | /var/lib/chio | Working directory for spool and persisted state. |
CHIO_TEE_CONFIG | /etc/chio/tee.toml | Path to the TEE config file inside the image. |
CHIO_TEE_MODE | verdict-only | Replay mode: emit only the allow/deny verdict, not the inputs. |
RUST_LOG | info | Tracing log level for the TEE runner. |
The TEE runner reads /run/chio-tee as a runtime socket directory and writes spool data to /var/lib/chio/tee. Both are created and chown'd to chio:chio in the image. See TEE Deployment for the attestation flow and the verdict-only vs full-replay modes.
Build Strategy
Workspace-aware caching
All three Dockerfiles copy the entire chio workspace, not just the crates the binary directly depends on. The reason is Cargo.lock resolution: examples/, formal/, and tests/ are workspace members; their absence breaks cargo build --workspace even though the build runs --package chio-cli. wit/ is consumed at compile time by chio-wasm-guards through wasmtime::component::bindgen!. sdks/ is copied for forward-compatibility with future Rust members.
BuildKit cache mounts
The distroless variant under deploy/sidecar/Dockerfile uses --mount=type=cache for the cargo registry and the build target directory:
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/build/target \
cargo build --release --locked -p chio-cli --bin chioCombined with cache-from: type=gha in the GitHub Actions workflow, this pulls warm cache layers from previous runs. CI builds drop from the eight-minute cold cargo compile to under two minutes on a warm cache.
--locked is mandatory
Every build invocation passes --locked. The image must compile against the committed Cargo.lock; an image that picks up newer transitive deps at build time defeats reproducibility and the SBOM contract.
Multi-Arch
The published sidecar image is built for linux/amd64 and linux/arm64. The CI workflow uses docker/setup-qemu-action plus docker/setup-buildx-action and passes platforms: linux/amd64,linux/arm64 to the build step.
The distroless variant's healthcheck stage handles multi-arch explicitly: it derives the Debian multiarch directory from dpkg-architecture -qDEB_HOST_MULTIARCH so the curl shared-library copy works unmodified on both x86_64-linux-gnu and aarch64-linux-gnu.
Image Tagging
The published image lives at ghcr.io/backbay-labs/chio-sidecar. The CI workflow emits four tag families through docker/metadata-action:
| Tag | Trigger | Example |
|---|---|---|
| Branch | Push to a branch | main |
| Semver full | Tag push vX.Y.Z | 0.4.2 |
| Semver minor | Tag push vX.Y.Z | 0.4 |
| Short SHA | Every push | sha-1a2b3c4 |
latest | Default branch only | latest |
latest does not track tag pushes
:latest on tag pushes. Tagging an older patch (e.g. v0.1.1) after a newer minor (e.g. v0.2.0) would otherwise regress :latest. Versioned tags still ship via the semver patterns; pin to those for production.Supply-Chain Signing
Every published sidecar manifest is keyless-signed with cosign through Sigstore Fulcio. The workflow exchanges its OIDC token for a short-lived Fulcio cert and signs the manifest by digest, which covers every tag pointing at the same content-addressed manifest.
# Workflow excerpt: sign the digest, not the tag.
ref="${IMAGE_NAME}@${IMAGE_DIGEST}"
cosign sign --yes "${ref}"Verification on the consumer side:
cosign verify ghcr.io/backbay-labs/chio-sidecar:latest \
--certificate-identity-regexp 'https://github\.com/backbay/chio/.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comThe build also emits a SLSA provenance attestation (provenance: mode=max) and an SBOM (sbom: true), both attached to the image manifest by docker/build-push-action. Workspace advisory hygiene is enforced separately at CI time through cargo-deny; see deny.toml for the active advisories and the migration plan for any current ignores.
Runtime Posture
The published images are designed to slot into hardened orchestrator manifests without surprises.
Non-root by default
- Alpine sidecar:
USER chio:chio(uid / gid 10001). - Alpine TEE: same
chio:chiouser. - Distroless sidecar:
USER 65532:65532(distroless nonroot).
Read-only root filesystem
The reference ECS task definition sets readonlyRootFilesystem: true and the non-root user user: "65532:65532". Reverting either knob is a regression. The kernel writes only to /var/lib/chio and a tmpfs for in-flight buffers.
Linux capability drops
The chio binary needs none of CAP_SYS_ADMIN, CAP_NET_RAW, or CAP_SYS_PTRACE. Drop all capabilities and re-add only CAP_NET_BIND_SERVICE when binding below port 1024. The default sidecar listen address is 9090, so even that is unneeded in the standard manifest.
Distroless considerations
deploy/sidecar/Dockerfile ships on gcr.io/distroless/cc-debian12:nonroot because the kernel binary is dynamically linked against libgcc_s on glibc. Distroless has no shell, so the Dockerfile bakes a static-relocated curl for the HEALTHCHECK probe; an exec-form CMD is used because there is no /bin/sh to run shell-form directives.
No exec into a distroless container
docker exec -it ... sh. Debug by reproducing on the Alpine variant, or by attaching a tracing sidecar that reads the kernel's structured logs.Next Steps
- Sidecar HTTP Service for the runtime config (env vars, listen address, health endpoints, supervision).
- TEE Deployment for the chio-tee image specifics (verdict-only mode, attestation flow, spool directories).
- Cloud Run for the Knative-style multi-container manifest.
- ECS Fargate for the task-definition manifest with
dependsOnhealth gating. - Azure Container Apps for the Bicep template with startup and readiness probes.