Skip to content
Registry StackDocsv0.38.0

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 package 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 package 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 -> /etc/registry-evidence/package 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 package 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>:/etc/registry-evidence/package: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 \
check --runtime-config /etc/registry-evidence/runtime.yaml --require-runtime-dependencies

It prints one line and exits zero:

Evidence package <package-digest> passed check (<count> requirements)

The digest is your own package’s, and the count is the number of requirements in that package, so the line will not match anyone else’s.

Both forms of the command compile the verified package, 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: the Transit provider did not answer on the configured Unix socket (missing socket, refused connection, or timeout).

--require-runtime-dependencies adds the dependencies a served request would need. It opens the configured audit writer exactly as serve would: creating the audit file and its lock if they are not already there, and deleting any sealed file already past audit.retainDays. It fetches the access-token issuer key set fail-closed and resolves every configured source credential. It appends no audit event of its own, but run beside a running service it refuses instead, because the writer is a single-writer destination and the running service already holds its lock.

Opening the audit path takes its single-writer lock, so a candidate that shares that path with a running instance is refused with another process holds the single-writer lock beside the audit file. To check the candidate before cutover without stopping that instance, add --without-audit-lock. The audit hash key is still checked, and the audit directory and files are still checked for ownership, mode, write access, and a complete final entry. The lock stays with the running instance.

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

Terminal window
docker compose up -d
docker compose ps

The maintained image’s default command is serve --runtime-config /etc/registry-evidence/runtime.yaml, the path the runtime file is mounted at. 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 package digest, 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.

Keep package and requirement digests distinct

Section titled “Keep package and requirement digests distinct”

The package digest covers the exact SHA256SUMS bytes and remains the same in host and Compose deployments. Runtime paths, listener bindings, trust files, secrets, and audit contents remain outside that package identity. A configuration revision is narrower than the package digest: it covers one requirement’s own configuration and artifacts. Signed assertions carry the revision of the requirement they answer as configurationRevision, never the whole-package digest. Neither digest 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 log 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 audit file rather than beginning a new one.