Containers - images and supply chain
Layered images inherit privileges: CVE scanning, signing and a rootless runtime shrink the surface the container hands to the host.
What it is
A container is a Linux process isolated by namespaces and cgroups, running a filesystem stacked in layers. Each layer is a decision: the base you pull, the apt install you run, the user that opens the shell, the binary that goes in. Each one becomes part of the surface the attacker finds.
Two principles solve 80% of the defense: minimal images (less stuff, fewer bugs) and a hardened runtime (less privilege, fewer paths). The rest is process: scanning, signing, policy.
Minimal base image
The base image is the foundation of everything that follows. ubuntu:latest or python:latest bring a shell, a package manager and a set of CVEs that grows every week. For production, choose a base that has only what the binary needs to run.
- distroless (gcr.io/distroless/*): no shell, no apt, no package manager. For static Go, Java, Node, Python applications
- alpine: busybox + musl, ~5MB, still has a shell but is much smaller than Debian
- scratch: nothing, just the binary. Valid for statically compiled Go/C/Rust binaries
- wolfi/chainguard: distroless with an audited supply chain and daily rebuilds
Trusted registry and signing
Knowing where it came from is worth more than knowing what is inside. Private registries (ECR, GCR, Harbor) give control over who publishes, with SSO and audit. Signing proves integrity: the image that is running is exactly the one CI built.
Cosign (from the Sigstore project) signs and verifies OCI images. The key pair can come from the team itself, from a (key stays in an HSM), or from Fulcio, which issues an ephemeral certificate based on an OIDC login (useful for CI without secrets).
# sign on push
cosign sign --key cosign.key ghcr.io/loja/checkout@sha256:abc123
# verify before starting (admission controller or pre-deploy job)
cosign verify \
--key cosign.pub \
--certificate-identity-regexp 'https://github.com/loja/checkout/.github/workflows/release.yml@refs/tags/.*' \
ghcr.io/loja/checkout@sha256:abc123Image scanning: finding CVEs before shipping
Scanners compare the image's (or each layer) against vulnerability databases (, GHSA, OSV). The output is the list of packages and CVEs, with severity and fixed version. It runs at three moments: at build (blocks a PR with a critical CVE), in the registry (finds a new CVE against an already published image) and at admission (rejects a pod with an unscanned image).
- Trivy: a single scanner (FS, image, , SBOM), runs offline, no central database
- Grype (Anchore): focus on images and CycloneDX SBOM
- Docker Scout: integrated with Docker Hub, uses the SBOM that Docker builds natively
- Snyk Container: commercial, good for teams already using Snyk for
Run as non-root and harden the runtime
A container running as UID 0 on the host is a root process on the host. If the attacker escapes the container, they already have administrative privileges on the node. Same with writable filesystems: it marks where the attacker will try to persist. Hardening the runtime is cutting paths before they exist.
FROM python:latest
RUN apt-get update && apt-get install -y curl netcat
COPY . /app
WORKDIR /app
EXPOSE 8080
CMD ["python", "app.py"] # runs as root (UID 0)# minimal base, no shell, multi-stage so dev deps are not inherited
FROM python:3.12-slim AS builder
WORKDIR /build
RUN pip install --no-cache-dir -r requirements.txt
FROM gcr.io/distroless/python3-debian12
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY . /app
USER 65532 # fixed non-root UID
EXPOSE 8080
ENTRYPOINT ["python", "/app/app.py"]Where it fits
Containers touch build, deploy and runtime. At build, the scan gates the PR. In the registry, signing authenticates the origin. At admission, the cluster rejects an unsigned image or one with a policy violation. At runtime, the watches behavior (next topic). Skipping any of these links opens a window.
Strengths
- immutability: the same image that was tested is the one that ships, with no 'works on my machine'
- minimal images cut CVEs by an order of magnitude: distroless Python has ~20 packages, Debian has ~600
- signatures and SBOM give real traceability, piece by piece, down to the installed package
Limitations
- scanning finds known CVEs: a in a dependency gets through, and signing does not help with that
- distroless and scratch make debugging harder (no shell). It is the trade-off between security and ergonomics, not a bug
- runtime policies (non-root, read-only fs) must be accepted by the application: not every binary starts without root, and rewriting takes time
connections on the map
- CWPP - workload protectionmonitors the runtimeContainers - images and supply chain
- Network - segmentation and zero-trustisolates the networkContainers - images and supply chain
updated on 14/10/2026 · sources to go deeper · report an error
in other domains
- Containers and imagesdevopsthe good image and the secure cluster: two halves of the same problem
- Kubernetes fundamentalsdevopsthe same cluster seen as a platform and as an attack surface
- SCAappsecfeeds into: the image inherits the dependencies
- SBOMappsecinventories what goes inside the image
Study before
Unlocks
test what you learned
Loading questions…