Supply chain: digests, SBOMs and signing
Knowing what is in an image, and proving it is the one you built.
Open this lesson in the learning hubKey points
- A tag is mutable.
myapp:1.4.2can be repushed with entirely different content, so a tag is a convenience, not an identity. Only the digest identifies an image. - Deploy by digest in production. It is what guarantees the artefact that passed your tests is the artefact that runs, with no window for substitution.
- An SBOM lists everything inside the image. Without one, answering "are we affected by this CVE" means rebuilding and scanning every image rather than querying an inventory.
- Generate the SBOM at build time, when the build tool knows the real dependency graph. Inferring it afterwards from a finished image is guesswork about what a jar actually contains.
- Signing proves an image came from your pipeline. Keyless signing with an OIDC identity avoids managing a private key, and records the signature in a public transparency log.
- The controls compose: an admission policy that requires a valid signature and a known-good provenance attestation is what stops an unreviewed image running at all.
Example
# A tag is not an identity. Resolve it and use the digest:
$ docker buildx imagetools inspect registry.example.com/app:1.4.2
# Name: registry.example.com/app:1.4.2
# Digest: sha256:9f2c... <- THIS is the artefact
# Deploy by digest, not tag:
# image: registry.example.com/app@sha256:9f2c...
# SBOM and provenance, produced by the build itself:
$ docker buildx build --sbom=true --provenance=mode=max \
-t registry.example.com/app:1.4.2 --push .
$ docker buildx imagetools inspect registry.example.com/app:1.4.2 \
--format "{{ json .SBOM }}" | jq '.SPDX.packages[].name' | head
# Keyless signing - no private key to manage or leak:
$ cosign sign registry.example.com/app@sha256:9f2c...
$ cosign verify registry.example.com/app@sha256:9f2c... \
--certificate-identity-regexp 'https://github.com/myorg/.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
# Scan, and fail the build on what is actually fixable:
$ trivy image --severity HIGH,CRITICAL --ignore-unfixed \
--exit-code 1 registry.example.com/app:1.4.2
# --ignore-unfixed matters: failing on CVEs with no available patch
# trains everyone to ignore the scanner, which is worse than no scanner.
---
# The question an SBOM answers in minutes rather than days:
#
# "A CVE just landed in a logging library. Which of our 200 images
# contain an affected version, and which are actually deployed?"
#
# With an SBOM inventory that is a query. Without one it is a project.
Tags are mutable and digests are not - deploy by digest, build the SBOM during the build, and verify signatures at admission.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Docker course, and every lesson in it is listed on the Docker contents page.