Abhinav C

Your container image is a product you ship to yourself

FROM python:3.12 looks like one line of code. It pulls in a full Linux distribution, a C library, OpenSSL, a shell, a package manager and a few hundred other packages. Your service uses a fraction of them. You are responsible for all of them.

For years that was a private concern. It’s becoming a public one. The EU’s Cyber Resilience Act requires manufacturers of products with digital elements to report actively exploited vulnerabilities starting in September 2026, with the rest of its obligations following in December 2027. Large customers increasingly ask suppliers for a software bill of materials as part of procurement. “We don’t know exactly what’s in our images” is turning into a compliance problem, not just a security one.

Know what’s inside: SBOMs at build time

A software bill of materials lists every package in an image and its version. Generate it when you build, not later by scanning whatever is running:

docker buildx build \
  --sbom=true \
  --provenance=mode=max \
  -t registry.example.com/orders:1.42.0 --push .

BuildKit attaches the SBOM and a provenance attestation (which source, which build, which steps) to the image in the registry. When the next widely exploited vulnerability is announced, the question “which of our images contain this package?” becomes a query instead of a week of scanning.

Ship less

Each package you don’t ship is a vulnerability you never have to patch.

  • Multi-stage builds. Compile in a full image, then copy only the result into a minimal runtime image. Compilers, package managers and build tools stay behind.
  • Minimal base images. Distroless or similarly slim images contain the runtime and little else: often no shell and no package manager. Scanners have far less to report, and an attacker has far less to work with.
  • Non-root by default. USER 10001 in the Dockerfile, and a read-only root filesystem in the deployment. Most services never needed root. They got it because it was the default.

Make builds reproducible enough to trust

  • Pin base images by digest, not just by tag. Tags can move. python:3.12-slim@sha256:... always means the same bytes. Let a bot open pull requests to update the digest, so updates are deliberate and reviewed.
  • Rebuild regularly, even without code changes. An image built six months ago contains six months of unpatched vulnerabilities. A weekly rebuild-and-redeploy keeps the base current.
  • Sign what you ship. Sign images in CI (for example with Sigstore’s cosign) and have the cluster refuse unsigned images. Then a compromised registry credential isn’t enough to run code in production.

Treat the image as a release

The useful mental shift is that an image is a product you ship, even if the only customer is your own cluster. Products have a bill of materials, a known origin, a support window and a recall process. Images built the same way are easier to secure, easier to audit, and much easier to explain when someone outside engineering starts asking.

Filed under: dependencies of dependencies.

More writing