CoursesAdvanced container securityMinimal & secure images

Trusting a minimal image: pin, SBOM, scan

Provenance for images with no package database.

Advanced12 min · lesson 9 of 25

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.

TRUST A MINIMAL IMAGE, END TO END
1Pin base by digest
FROM ...@sha256 — same bytes on every rebuild
2Generate SBOM
syft — list every component, even on scratch
3Scan the SBOM
grype/trivy match CVEs, --fail-on high
4Sign the digest
cosign keyless — OIDC identity, Rekor log
5Verify at deploy
cosign verify at admission; reject on mismatch
6Rescan & rebuild
on a cadence — new CVEs land against old images
Minimal, trustworthy, and current are three different properties; a hardened supply chain needs all three.
Dockerfile
# 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 /server
USER nonroot
ENTRYPOINT ["/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.

terminal
$ 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.

terminal
$ 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".

terminal
# 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; do
echo "== $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
A tiny image is not automatically a trusted one
Small reduces surface, but it says nothing about origin or freshness — a 4MB image can still be the wrong 4MB, or a right one that is now six months of CVEs behind. Pin by digest, generate and scan an SBOM, verify a signature, and rescan-and-rebuild on a cadence. Minimal, trustworthy, and current are three different properties; a hardened supply chain needs all three.