Skip to content
Registry StackDocsv0.46.0

Integrate a Registry Evidence candidate with Docker Compose

For the operator

View as Markdown

You have a candidate from rstack evidence package that signs through a Transit proxy, and you run containers with Docker Compose. Run that candidate unchanged, with the runtime, secrets, Transit socket, and audit storage each mounted separately; the package digest stays the same as on a host deployment.

  • The candidate directory, holding bundle/, runtime.yaml, and compiled-authority.yaml.
  • A reviewed Registry Evidence image, pinned by digest.
  • A running Transit proxy whose socket directory you can mount, set up as in Configure Transit signing for Registry Evidence.
  • An owner-only secret directory and storage that UID and GID 65532 can write.
  • The Compose adapter from your release’s source archive, https://github.com/registrystack/registry-stack/archive/refs/tags/v<version>.tar.gz, where <version> is the version rstack evidence --version prints. The archive unpacks to registry-stack-<version>/, and the adapter is its docker/ directory. Copy that directory to evidence-compose/, your operator-owned copy, and run every command from inside it.

The Compose file is compose/docker-compose.yaml in evidence-compose/. You own it; rstack evidence package does not write it. It reads five variables:

Terminal window
export EVIDENCE_CANDIDATE_DIR='<candidate>'
export EVIDENCE_RUNTIME_FILE='<container-runtime.yaml>'
export EVIDENCE_SECRET_ROOT='<secret-root>'
export EVIDENCE_TRANSIT_SOCKET_DIR='<transit-socket-directory>'
export EVIDENCE_IMAGE='<reviewed-evidence-image-by-digest>'
docker compose -f compose/docker-compose.yaml config

EVIDENCE_CANDIDATE_DIR is the whole candidate directory, the one containing bundle/ and compiled-authority.yaml. The adapter mounts its bundle read-only at /etc/registry-evidence/bundle, and the runtime’s package.root must name that directory. The adapter mounts the container runtime file at /etc/registry-evidence/runtime.yaml and the secret root at /run/secrets/registry-evidence, both read-only, the Transit socket directory read-only at /run/registry-evidence, and the evidence-audit named volume at /var/lib/registry-evidence, its only writable mount.

The service runs as 65532:65532 with read_only, cap_drop: [ALL], no-new-privileges, and a read-only /dev/shm tmpfs. Registry Evidence receives the proxy socket, never a provider token.

Start the container runtime file from compose/runtime.docker.yaml. It already names the container paths the adapter mounts, binds 0.0.0.0:8080 under networkExposure: container-private, and selects a Transit signer. Set signer.keyName and signer.keyVersion to the values in your candidate’s runtime.yaml; the adapter’s copy pins key version 1.

A packaged candidate is refused until its target authority is activated, and the adapter’s runtime file carries no authority block. Add one yourself. Keep its root inside the audit volume, because the maintained preflight accepts only one writable mount, and set target to the id in your target.yaml:

authority:
root: /var/lib/registry-evidence/authority
target: <target-id>

Skip this step unless a source pack uses a sqlite-extract endpoint. Each such endpoint names sqliteExtract.extractProfile, and the candidate’s runtime.yaml carries a sourceExtracts binding for each profile, with host paths. Carry every binding into the container runtime file under the same names, pointing at container paths:

sourceExtracts:
<extract-profile>:
path: /etc/registry-evidence/extracts/<extract-file>.sqlite

The names must match the bundle’s extractProfile names exactly: the container check and startup refuse a missing one as evidence.runtime.source-extract-unbound and an extra one as evidence.runtime.source-extract-unused. Then mount each extract file read-only at its path, in the evidence service’s volumes in your copy of the adapter:

- <host-extract-file>:/etc/registry-evidence/extracts/<extract-file>.sqlite:ro

The file must be readable by UID 65532. Evidence opens an extract as immutable and refuses a symbolic link or a writable file; the read-only mount satisfies the second, and the preflight refuses a writable mount under /etc. Keep extracts out of /var/lib/registry-evidence: that is the audit volume, the one writable mount. Publish a replacement extract as a new file and restart, never by changing the mounted one.

Startup refuses the runtime file unless it and every directory above it, as the container sees them, are owned by root or the runtime user (UID 65532 in the images) and not writable by group or others. A symbolic link anywhere in the path is refused. Docker shows the container the owner the file has on the host, so a file you wrote as yourself fails with platform.runtime-config.unsafe-file. The adapter mounts this one file under directories the image owns, so only the file needs changing. Do this once your edits are finished:

Terminal window
sudo chown 65532:65532 "$EVIDENCE_RUNTIME_FILE"
sudo chmod 0400 "$EVIDENCE_RUNTIME_FILE"

Use sudo to edit the file after this.

Run the one-shot activation service inside the container, so the authority store is created and owned by the service identity. The service mounts the candidate’s compiled-authority.yaml and the authority volume writable for this operation:

Terminal window
docker compose -f compose/docker-compose.yaml run --rm authority-activate
ACTIVATED sha256:<authority-revision> sha256:<authority-digest> sequence=<n>

The activation persists in the volume, so later runs and up read the same authority. See evidence authority activate.

Start the Transit proxy first, then run the check in the container:

Terminal window
docker compose -f compose/docker-compose.yaml run --rm evidence \
check --runtime-config /etc/registry-evidence/runtime.yaml \
--require-runtime-dependencies \
--require-audit-under /var/lib/registry-evidence
Evidence package <package-digest> passed check (<count> requirements)
0 errors, 0 warnings in <count> files

--require-runtime-dependencies validates the mounted secrets and signs a self-test message through the proxy, so a missing socket fails with evidence.deployment.signing-unavailable. It also checks the configured runtime dependencies and applicable extract freshness on this host. It does not query source data endpoints or guarantee that a source will remain available. --require-audit-under refuses an audit path that does not resolve inside the volume. See evidence check.

The adapter’s preflight also checks the image pin, the mounts, and the service posture, then runs the same native check:

Terminal window
python3 runtime-preflight.py \
--compose-file compose/docker-compose.yaml \
--service evidence=evidence

Changing only the container runtime file leaves the package digest unchanged, so fixtures need not run again; Build and deploy a Registry Evidence project explains the digests.

Terminal window
docker compose -f compose/docker-compose.yaml up -d
docker compose -f compose/docker-compose.yaml logs evidence

The image’s default command is serve --runtime-config /etc/registry-evidence/runtime.yaml, so up needs no arguments. The log carries an evidence service listening record once every listener is bound; if it is missing, the log names the startup failure. The adapter publishes no port, so reach the listener from another service on the Compose network and put TLS termination ahead of it. A passing /ready healthcheck establishes the runtime’s readiness boundary, not upstream source-data availability. Run the target-host preflight above before deployment. A stale or unavailable source can refuse evaluations that need it while other sources remain serviceable. Keep the operator contract as the owning definition of readiness and dependency checks.

Use the safe source diagnostic categories and value-free audit outcomes for alerts. Alert on sustained dependency failures and audit/readiness faults without exporting selectors, source responses, credentials, or raw transport errors. Follow the configured operator recovery and retry budget; an unavailable upstream does not establish a recovery time, so do not invent a Retry-After interval.

docker compose down removes the containers and the network and keeps named volumes, so the audit log and the activated authority survive.

Rotate Registry Evidence signing keys