Supply chain: digests, SBOMs and signing

Docker · lesson 31 of 31 · 5 min read

Knowing what is in an image, and proving it is the one you built.

Open this lesson in the learning hub

Key points

  • A tag is mutable. myapp:1.4.2 can 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.