Agent image digest pinning
The agent-pi Docker image runs every practice-review sandbox and every Pi mentor container. Production references it by sha256 digest so the bytes can't change under a running deploy and GHCR retention can't break sandboxes between releases.
Prerequisites
- Docker Compose v2.24+ on the deploy host (long-form
env_file: [{path, required}]syntax). - GHCR retains
:vX.Y.Ztags. Each release tags the agent-pi manifest; as long as those tags exist, the digest is retained. Verify thels1intum/hephaestus/agent-pipackage isn't configured to prune versioned tags. - Coolify preserves the
env_file:directive. Validate by setting the file to a sentinel value anddocker compose exec application-server envafter a deploy. HEPHAESTUS_AGENT_IMAGE_REFERENCEis not set in the Coolify UI orcompose.app.yaml'senvironment:block. Compose precedence isenvironment:>env_file:, and shell-interpolated${VAR}shadows both.
Source of truth: docker/agent-image-pin.env
HEPHAESTUS_AGENT_IMAGE_REFERENCE=ghcr.io/ls1intum/hephaestus/agent-pi@sha256:<digest>
compose.app.yaml loads it via env_file: (with required: false), followed by an optional docker/agent-image-pin.local.env (gitignored) for local overrides. The application server reads the final value into hephaestus.agent.image.reference; application-prod.yml sets hephaestus.agent.image.require-digest: true, so AgentImagePinGuard refuses to start on a tag reference.
For local agent-pi iteration: build your image and drop a one-line override into docker/agent-image-pin.local.env:
docker build -t ghcr.io/ls1intum/hephaestus/agent-pi:dev docker/agents -f docker/agents/pi/Dockerfile
echo 'HEPHAESTUS_AGENT_IMAGE_REFERENCE=ghcr.io/ls1intum/hephaestus/agent-pi:dev' > docker/agent-image-pin.local.env
How the pin gets updated
.github/workflows/release.yml runs on every semver release:
- Retag
docker buildx imagetools create -t agent-pi:vX.Y.Z agent-pi:<sha>. - Verify the OCI manifest digest (
sha256ofimagetools inspect --raw) for source and target tags match — catches any registry-side rewrite. - Smoke the published digest by pulling and running
bun --version && node --version. - Commit the new line to
docker/agent-image-pin.envand push tomainwith[skip ci](race-tolerant: rebase-and-retry up to 3 times).
Images are cosign-signed (keyless OIDC) and provenance-attested by reusable-docker-build.yml, then verified in the same job.
Verification
DIGEST=$(grep -oE '@sha256:[a-f0-9]{64}' docker/agent-image-pin.env)
IMAGE=ghcr.io/ls1intum/hephaestus/agent-pi${DIGEST}
cosign verify "$IMAGE" \
--certificate-identity-regexp '^https://github.com/ls1intum/hephaestus/' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com'
gh attestation verify "oci://$IMAGE" --owner ls1intum
git log --oneline -- docker/agent-image-pin.env
gh release view vX.Y.Z
Rollback procedure
git log --oneline -- docker/agent-image-pin.env
git revert <bad-release-commit>
git push origin main
Coolify redeploys from main; new sandboxes pull the rolled-back digest. In-flight mentor containers keep their already-pulled image until they restart.
Note: the broken :vX.Y.Z tag still points to the bad digest — git revert only moves what main references. If anything else pins by version tag, cut a vX.Y.Z+1 patch release once the fix is committed.