Trusting a minimal image: pin, SBOM, scan
Provenance for images with no package database.
A minimal image raises a fair question: if there is no package manager, how do you know what is inside and whether to trust it? The answer is provenance — pin the exact bytes, generate a bill of materials, scan it, and verify a signature. A scanner cannot query a package database that is not there, so on distroless and scratch images you lean on an SBOM built at build time and on signatures that prove where the image came from.
# pin the base by digest so "rebuild" means the same bytes, not "whatever is latest"FROM gcr.io/distroless/static-debian12@sha256:d71f4f2a9c...COPY --from=build /server /serverUSER nonrootENTRYPOINT ["/server"]
SBOM: know what is inside
An SBOM (software bill of materials) lists every component and version in the image, generated by scanning the artifact and its build inputs — syft is the common tool, and BuildKit can attach an SBOM as an attestation at build time. With the SBOM in hand, a scanner like grype or trivy matches it against vulnerability feeds, so you get real CVE results even for a scratch image with no on-disk package DB.
$ syft svc:1.0 -o spdx-json > sbom.json # what is actually in there$ grype sbom:sbom.json --fail-on high # scan the SBOM, gate the build# or attach it at build time:$ docker buildx build --sbom=true --provenance=true -t svc:1.0 --push .
Sign, and verify at deploy
Signing binds the image digest to an identity so a consumer can prove it built where you think it did. Cosign keyless signing uses your CI’s OIDC identity and a transparency log — no long-lived key — and the same tooling verifies the signature (and the SBOM/provenance attestations) before anything runs. Pin by digest, ship an SBOM, sign the digest, verify at admission: that is a trustworthy minimal image end to end.
$ cosign sign --yes svc@sha256:9f2a... # keyless, logged in Rekor$ cosign verify \--certificate-identity-regexp ".*@acme.internal" \--certificate-oidc-issuer https://gitlab.acme.internal \svc@sha256:9f2a... # non-zero exit = reject
Rescan and rebuild on a cadence
Provenance is not a one-time gate — an image that was clean at build time accumulates CVEs as new vulnerabilities are disclosed against packages it already contains. So the lifecycle continues after deploy: rescan the images you are actually running on a schedule (and on every new critical CVE), and rebuild against a patched base on a regular cadence rather than waiting for an incident. A minimal image helps enormously here too — fewer packages means fewer CVEs to react to — but "few" is not "none".
# scan what is actually running, not just what CI built — new CVEs land against old images$ docker ps --format '{{.Image}}' | sort -u | while read img; doecho "== $img"; trivy image -q --severity HIGH,CRITICAL --ignore-unfixed "$img"done# then rebuild on a cadence so the pinned base is refreshed deliberately:# bump FROM ...@sha256 to the latest patched digest, rebuild, rescan, re-sign, redeploy