Why and how do you scan container images for vulnerabilities before they reach production?
Every container image is a frozen stack of someone else's dependencies, and any layer in it can carry a known, disclosed CVE nobody on your team has ever read about.
LEARN BY DOINGPut your intuition to work3 steps
Why and how do you scan container images for vulnerabilities before they reach production?
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.#A container image is a frozen stack of someone else's decisions
When you FROM node:20-slim or FROM eclipse-temurin:21-jre, you're not just picking a runtime — you're inheriting a base operating system, its package manager's installed packages, and whatever version of every transitive library was current the day that base image was built. Your own application code is usually the smallest, most-reviewed layer in the image. The base OS packages, the language runtime, and your dependency tree are the layers nobody on your team wrote and almost nobody reads line by line — and any one of them can be shipping a Common Vulnerabilities and Exposures (CVE) entry that was published after the image was built.
Image scanning exists to answer one question before a workload ever reaches a cluster: does this exact set of layers, at these exact pinned versions, contain a known, disclosed vulnerability?
#How a scanner actually inspects an image
Container images are layered filesystems — each RUN, COPY, or ADD instruction in a Dockerfile produces a layer, and the final image is those layers stacked with a manifest describing the order. A scanner doesn't run the image; it statically unpacks it and walks it like a filesystem, looking for known package databases:
- OS-level package managers (dpkg for Debian/Ubuntu, rpm for RHEL/Alpine's apk, etc.), which reveal exact installed OS package versions.
- Language-level dependency manifests baked into the image (e.g., a
package-lock.json,requirements.txt,go.sum, or the JARs in a Java image's classpath).
From this, the scanner builds (or reads, if you generated one at build time) a Software Bill of Materials (SBOM) — a structured, machine-readable inventory of every package and version present in the image, per layer. The SBOM is then checked against vulnerability databases: the National Vulnerability Database (NVD), GitHub Security Advisories (GHSA), and distro-specific advisories (Debian Security Tracker, Alpine's secdb, Red Hat's OVAL feeds). Each match gets a CVE identifier and a CVSS severity score (Critical/High/Medium/Low), and the scanner reports which layer introduced it.
#Common tools and where scanning happens
Popular scanners include Trivy, Grype, Clair, Docker Scout, and commercial options like Snyk Container. They differ mostly in database freshness, speed, and SBOM format support (SPDX, CycloneDX) — the underlying approach is the same. Scanning should happen at three points, not just one:
- Locally / pre-commit. A developer scans before pushing, catching an obviously outdated base image early, when it's cheapest to fix.
- CI pipeline gate. After the image builds, the CI job scans it and enforces policy before the image is pushed to the registry or deployed — this is the highest-leverage checkpoint because it's a hard stop everyone goes through.
- Continuous registry scanning. Images already sitting in the registry, deployed weeks ago, are rescanned periodically, because new CVEs get disclosed against old, unchanged software constantly. An image that was "clean" on the day it was built can become vulnerable a month later with zero code changes on your side.
#Making the CI gate actually block the pipeline
A scan that only produces a report nobody reads is theater. The real engineering decision is the policy: what severity fails the build, and what's the exception process. A typical policy:
- Fail the build on any Critical finding, no exceptions without an explicit, time-boxed waiver.
- Fail on High findings unless an approved fix isn't yet available upstream, in which case the team files a documented, expiring exception (with a ticket and a re-check date) rather than silencing the finding forever.
- Allow Medium/Low to pass with visibility (dashboard, ticket) but not block deploys.
- Track "days since last successful full-clean scan" as a metric — a growing exception list is itself a signal that base images are stale.
Waivers need an expiry date. A permanent .trivyignore entry for a CVE is exactly how "temporary" exceptions become permanent, silent risk.
#Triage: not every CVE is exploitable in your context
A scanner reports a package-version match — it has no idea whether your application ever calls the vulnerable code path. A CVE in a compression library's rarely-used decompression function might be irrelevant if your app never decompresses untrusted input. This is why scanning output needs a human triage step, not blind auto-blocking on every Critical: teams that treat every finding as equally urgent burn out on alert fatigue and start reflexively approving waivers, which defeats the entire control.
🧠 Check your understanding
0/2 · 0/2 answered1. A container image scanner primarily works by:
2. Why is continuous re-scanning of already-deployed images necessary, even with no code changes?