Container Image Scanning Done Right: Triage CVEs Without Drowning in Noise
Image scanners produce hundreds of CVEs and almost no priorities. Here's how I scan with Trivy, fix what matters, and use AI to triage findings into a real action list.
- #security
- #hardening
- #containers
- #vulnerability-scanning
- #trivy
- #ai
The first time a team runs a container image scanner, the reaction is always the same: panic, then paralysis. One image, 340 CVEs, half of them “CRITICAL,” and no obvious place to start. A week later the scan is still red, nobody’s fixed anything, and the dashboard has become wallpaper everyone learned to ignore.
The problem isn’t the scanner. It’s that raw CVE output is data, not priorities. After 25 years of hardening production images, here’s how I scan, how I separate the CVEs that matter from the noise, and how I use AI to turn a 340-line report into a short, ordered action list.
Scan early, scan in the pipeline
The cheapest vulnerability to fix is the one you catch before it ships. I run Trivy at three points: locally while building, in CI as a gate, and continuously against what’s already deployed (because new CVEs get disclosed against images that were clean yesterday).
# Scan an image, fail the build on fixable high/critical issues
trivy image --severity HIGH,CRITICAL \
--ignore-unfixed \
--exit-code 1 \
myorg/api:1.4.2
Two flags do most of the work here. --severity HIGH,CRITICAL cuts the low-severity noise you’ll never action. --ignore-unfixed hides CVEs with no available patch — there’s nothing to do about those right now, so they shouldn’t block your build or clutter your triage.
Cut the noise at the source: smaller base images
The single most effective way to reduce CVE count is to ship less. A python:3.12 image carries an entire Debian userland — compilers, shells, package managers — most of which your app never touches but every CVE scanner counts.
- Use distroless or slim/alpine base images so there’s simply less software to be vulnerable.
- Use multi-stage builds: compile in a fat builder image, copy only the artifact into a minimal runtime.
- Pin base images by digest (
FROM image@sha256:...) so your “clean” scan stays meaningful.
FROM golang:1.22 AS build
# ...compile...
FROM gcr.io/distroless/static-debian12
COPY --from=build /app /app
ENTRYPOINT ["/app"]
I’ve watched a single base-image swap drop a report from 300+ CVEs to a dozen. That’s not cheating the scanner — it’s genuinely reducing attack surface.
Triage: not every CRITICAL is critical to you
A CVE’s CVSS score describes the vulnerability in the abstract. Whether it matters to you depends on context the scanner doesn’t have:
- Is the vulnerable code path reachable? A CVE in a library you import but never call is real, but it’s not urgent.
- Is it network-exposed? A flaw in a parser that touches untrusted input is a different priority than one in a build-time tool.
- Is there a fix? Unfixable CVEs go to a watchlist, not the top of the queue.
- What’s the deployment context? An internet-facing API and an internal batch job with the same CVE warrant different urgency.
This contextual judgment is exactly what turns a wall of red into a plan — and exactly where most teams stall.
Using AI to triage the report
Here’s where AI earns its place. It can’t decide your risk tolerance, but it’s excellent at structuring a giant report into the dimensions above so a human can decide fast.
I export the scan as JSON and prompt:
“Here is a Trivy JSON report for a production container image. Group the findings by package, and for each high/critical CVE tell me: whether a fixed version exists, the simplest remediation (base image bump, package upgrade, or rebuild), and whether the vulnerable component is likely reachable for a typical web API. Produce a prioritized action list, fixable-first. Don’t invent CVE details not in the report.”
That last clause matters — models will confidently fabricate CVE specifics if you let them, so anchor it to the report data and verify anything load-bearing against the official advisory.
What comes back is the thing the raw report never gives you: an ordered list. “Bump the base image to 12.5 — fixes 40 of these. Upgrade libxml2 — fixes the reachable one. The rest are unfixable, watchlist them.” That’s a morning’s work instead of a paralyzed week.
You can keep this triage prompt with your other security hardening prompts so the whole team triages consistently rather than each person re-deriving priorities.
Gate the pipeline, but gate it sanely
Once you understand your images, enforce a policy in CI. The trick is gating on what’s actionable:
# Block only fixable high/critical CVEs — don't block on the unfixable
trivy image --severity HIGH,CRITICAL --ignore-unfixed \
--exit-code 1 myorg/api:${TAG}
A gate that blocks on unfixable CVEs trains engineers to bypass the gate. A gate that blocks only on things they can actually fix gets respected. For the broader pipeline-side controls this fits into, our Code Review tool and a CI scan step cover complementary ground — code review for the Dockerfile, scanning for the resulting image.
Don’t forget runtime
Scanning catches known CVEs in your image. It doesn’t catch a container that’s been compromised at runtime. Pair scanning with basics: run as non-root, drop Linux capabilities, mount root filesystems read-only, and don’t bake secrets into layers.
USER 10001
# at runtime: --read-only --cap-drop=ALL
The short version
Scan early and continuously, but cut the noise at the source with minimal base images and multi-stage builds. Filter to fixable high/critical findings so your queue is actionable. Use AI to turn a sprawling JSON report into a prioritized, fixable-first action list — anchored to real report data, verified against advisories. Then gate CI on what engineers can actually fix, and harden the runtime so a clean scan isn’t your only defense.
AI-generated CVE triage is assistive, not authoritative. Always verify severity and remediation against the official advisory before acting on a production image.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.