# Base image digest ledger Every external base image referenced by `deploy/*.Dockerfile` must be pinned by an immutable `@sha256:` digest before a production release (`docs/operations/DEPLOYMENT_UNRAID.md` §6 "immutable release/image digests recorded"; `docs/architecture/SECURITY_THREAT_MODEL.md` §5 "immutable image digest in production record"). The digests below were resolved with `docker buildx imagetools inspect` on 2026-08-10. Do not hand-type a digest into this table or a Dockerfile without resolving it against the real registry first. ## Status | Image | Used in | Status | |---|---|---| | `golang:1.26.6-alpine` | `agent.Dockerfile:1`, `api.Dockerfile:1`, `migrate.Dockerfile:1`, `worker.Dockerfile:1`, `smoke-fixture.Dockerfile:1` (build stage), `postgres.Dockerfile:4` (`gosu-builder` stage) | DONE — pinned `@sha256:3889b425f035be855a72fb4755265311293b6d414521f0a519d819df32222d83` | | `alpine:3.22` | `agent.Dockerfile:8`, `api.Dockerfile:8`, `migrate.Dockerfile:8`, `worker.Dockerfile:8` (runtime stage) | DONE — pinned `@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce` | | `node:24-bookworm-slim` | `web.Dockerfile:4` (build stage) | DONE — pinned `@sha256:3638d9a6fe4030bd716be989438248074489337ba3275657f93595428be4fc03` | | `nginx:1.29-alpine` | `web.Dockerfile:10` (runtime stage) | DONE — pinned `@sha256:5616878291a2eed594aee8db4dade5878cf7edcb475e59193904b198d9b830de` | | `postgres:17-alpine` | `postgres.Dockerfile:26` (runtime stage) | DONE — pinned `@sha256:742f40ea20b9ff2ff31db5458d127452988a2164df9e17441e191f3b72252193` | ## Resolving a digest Requires network access and Docker. From a trusted machine (not necessarily this repo's build host): ```sh # Option A: buildx imagetools (works without pulling the image locally) docker buildx imagetools inspect golang:1.26.6-alpine # Read the top-level "Digest:" line for the manifest list, or the # platform-specific line under the linux/amd64 entry if this project only # ever builds for one architecture (check TARGETARCH usage in the # Dockerfile before choosing). # Option B: pull + inspect docker pull golang:1.26.6-alpine docker inspect --format='{{index .RepoDigests 0}}' golang:1.26.6-alpine ``` Repeat for every external registry image. Docker's built-in `scratch` rootfs is not a registry image and therefore has no manifest digest to pin. ## Applying a resolved digest 1. Edit the Dockerfile's `FROM` line to `FROM :@sha256:` (keep the tag alongside the digest for human readability — the digest is what actually pins the build). 2. Update the row above from `TODO: unresolved` to `DONE — pinned @sha256:` plus the resolution date. 3. Run `sh deploy/verify-image-digests.sh` and confirm it reports `OK`. 4. Record the resolved digest set in the deployment evidence per `docs/operations/DEPLOYMENT_UNRAID.md` §8 step 16 ("record digests/migration/config checksum"). ## Enforcement `deploy/verify-image-digests.sh` scans every `deploy/*.Dockerfile`, skips internal multi-stage references (`FROM `) and the built-in empty `scratch` rootfs, and fails (non-zero exit) if any external registry `FROM` line lacks `@sha256:`. All current external images are pinned and the gate passes. Run it as part of `docs/operations/DEPLOYMENT_UNRAID.md` §8 step 1 ("validate clean build and images") before any production build, and wire it into whichever CI/Makefile target performs that step (`make build`, `make compose-up`) as a blocking pre-flight — this repo's `Makefile`/CI config is outside `deploy/` so it is not modified by this change; that wiring is a follow-up.