Skip to content
Registry StackDocsDevelopment (unreleased)

Integrate an Evidence Gateway candidate with Docker Compose

For the operator

View as Markdown

Start with the approved candidate created in Build and deploy an Evidence Gateway project. This guide runs that candidate in an existing Docker Compose application. Compose is an operator-owned deployment adapter, not output from evidencectl package. The candidate bundle stays unchanged across host and container deployments.

Outcome
An approved Evidence Gateway candidate mounted unchanged with a separate runtime, secret root, Transit socket, and persistent audit volume.
Time
About 20 minutes after the image and candidate are approved
Level
Operator-owned Docker Compose deployment
Prerequisites
Docker ComposeAn approved Evidence Gateway candidateA reviewed Evidence Gateway imageA workload-local Transit proxyPersistent storage prepared for UID and GID 65532

You need an approved candidate, a container image whose provenance and digest you have reviewed, an owner-only secret mount, a workload-local Transit proxy, and persistent storage for the audit chain. Do not use repository development images as released production artifacts.

Prepare a container-specific runtime document. It names the same bundle content but container paths, a private Compose-network listener, secret root, audit path, and any required private CA files.

Production and evidence-grade candidates require a workload-local Vault or OpenBao Transit proxy. Run one proxy for Evidence Gateway on the host or in an operator-owned sidecar. Give the proxy a dedicated host directory in which to create transit-proxy.sock, then bind-mount that directory at /run/registry-evidence in the Evidence Gateway container. Make the directory searchable but not writable by the Evidence Gateway identity. The proxy owns the directory and creates a mode 0660 socket whose group admits only that identity.

The proxy owns its provider auto-auth credential, provider trust file, and reviewed HCL configuration. Do not mount those inputs into Evidence Gateway. The proxy configuration must force its auto-auth token, require the X-Vault-Request header, disable request retries, and set socket ownership for the Evidence Gateway process. Use Configure Transit signing for Evidence Gateway and the maintained deployment-target templates for the exact boundary.

The Git-managed proxy HCL is nonsecret. Provider credentials and generated tokens remain outside Git and outside the Evidence Gateway container. If Compose also owns the sidecar, replace the host bind with a dedicated named volume shared only by the proxy and Evidence Gateway.

The Evidence Gateway service mounts five independently owned paths:

candidate/bundle -> /etc/registry-evidence/bundle read-only
runtime.docker.yaml -> /etc/registry-evidence/runtime.yaml read-only
Evidence secret root -> /run/secrets/registry-evidence read-only
Evidence audit volume -> /var/lib/registry-evidence writable
Transit socket directory -> /run/registry-evidence socket access

Keep the bundle and runtime read-only. A read-only mount establishes their immutability, but does not waive secret ownership or mode validation. The maintained Evidence Gateway image runs as UID and GID 65532; pin that identity in the Compose file and make every secret file acceptable to it with the required owner-only permissions. A different reviewed image requires an explicitly reviewed UID and matching ownership. Prepare the persistent audit volume for that same identity before startup. Do not run Evidence Gateway as root to compensate for an audit volume with the wrong owner.

# Mounts and service identity only. This is not a complete service definition;
# the note below this block names what it leaves out.
services:
evidence:
image: <reviewed-evidence-image-by-digest>
user: "65532:65532"
read_only: true
volumes:
- <candidate>/bundle:/etc/registry-evidence/bundle:ro
- ./runtime.docker.yaml:/etc/registry-evidence/runtime.yaml:ro
- <evidence-secret-root>:/run/secrets/registry-evidence:ro
- evidence-audit:/var/lib/registry-evidence
- <transit-socket-directory>:/run/registry-evidence:ro

That snippet shows the mounts and the service identity only. The maintained adapter at docker/compose/docker-compose.yaml in the Registry Stack repository carries the rest of the service posture, including dropped capabilities, no-new-privileges, and a read-only /dev/shm that replaces the writable one Docker adds by default. Start from that file rather than from the mounts alone.

Bind Evidence Gateway to a private Compose-network address. Put TLS termination and public routing in an operator-controlled service ahead of that listener.

Start the Transit proxy, then run evidence check in the target execution context after mounts, ownership, paths, and trust files are in place:

Terminal window
docker compose run --rm evidence \
--runtime /etc/registry-evidence/runtime.yaml check --require-runtime-dependencies

It prints one line and exits zero:

Evidence deployment <bundle-revision> / <runtime-revision> passed check (<count> requirements)

Both revisions are your own candidate’s, and the count is the number of requirements in your bundle, so the line will not match anyone else’s.

Both forms of the command compile the bundle, compile the source plans, refuse a mounted extract already older than its source allows, validate the mounted secret material, and initialize the configured signer, which signs a self-test message through the Transit proxy and verifies it against the governed public JWK. That is why the proxy starts first: without a reachable socket either form stops at evidence: runtime signing initialization failed.

--require-runtime-dependencies adds the dependencies a served request would need. It opens the configured audit path, fetches the access-token issuer key set fail-closed, and resolves every configured source credential. It appends no audit event in either form, so a check that fails leaves the chain exactly as it found it.

Changing only the container runtime does not change the governed bundle, so it does not require the fixture suite to run again. Run fixtures again when the bundle changes.

Terminal window
docker compose up -d
docker compose ps

The service reads its runtime from REGISTRY_EVIDENCE_RUNTIME, which the maintained image already points at /etc/registry-evidence/runtime.yaml, and its default command is serve. No further arguments are needed.

Evidence Gateway writes line-delimited JSON to stdout. It announces the listener only after every listener is bound, so the announcement means the port is this deployment’s and not something else’s:

Terminal window
docker compose logs evidence | grep 'evidence service listening'

That record carries the bundle revision, the runtime revision, the bind host, and the port. If no such line appears, read the whole log: startup failures are reported there, and the container will have exited.

The runtime in this guide binds a Compose-network address with no published ports, so /health is reachable from another service on the same Compose network and not from your host. Publish a port only behind the TLS-terminating service you control.

The bundle revision covers exact bundle bytes and remains the same in host and Compose deployments. The runtime revision covers exact runtime bytes and bound private CA files, so it changes with container paths, listener bindings, or trust files. A configuration revision is narrower than either: it covers one requirement’s own configuration and artifacts. Signed assertions carry the revision of the requirement they answer as configurationRevision, never the runtime or bundle revision. No revision contains secret values or audit contents.

docker compose down removes the containers and the network it created. It leaves named volumes alone, so the audit chain survives the stop:

Terminal window
docker compose down
docker volume ls | grep evidence-audit

The volume is still listed. Starting the service again appends to the same chain rather than beginning a new one.