A practitioner's comparison of Trivy, Grype, and Snyk for finding CVEs in container images, plus how to wire scanning into CI without drowning in noise.
A container image is not a single thing you audited once. It is a stack of other people's decisions: a base image someone else built, an OS package tree that shipped with it, and the language dependencies your app pulled in at build time. Every one of those layers accumulates CVEs over time, and the clock never stops. An image that scanned clean in March is full of new findings by July, not because anything in it changed, but because the world learned more about what was already there.
That is why container vulnerability scanning is a continuous job, not a gate you pass once. This post covers what scanners check, compares the tools people reach for, and shows how to wire scanning into CI without burying your team in noise.
Three sources feed the pile:
Base image: If you start from ubuntu:22.04 or node:20, you inherit hundreds of installed packages you never chose. Most are fine. Some carry known vulnerabilities the moment you pull them.
OS packages: apt, apk, and yum install shared libraries (openssl, glibc, zlib, curl) that get CVEs disclosed against them constantly. Your Dockerfile might not touch them, but they are in the image and part of your attack surface.
Application dependencies: The npm, pip, go, and Maven packages your code imports, plus their transitive dependencies. This overlaps heavily with dependency and SCA scanning, and good container scanners cover it too.
The better tools go well past "list the CVEs." A full scan covers:
USER directive, world-writable files).Not every tool does all five, and that spread is most of the difference between them.
Open source, fast, and broad. Trivy scans OS packages, language deps, IaC, secrets, and licenses in one binary with no server to run. It has become the default in most CI pipelines because it is trivial to install and covers the widest surface for free. A basic scan:
$ trivy image myapp:1.4.2
myapp:1.4.2 (debian 12.4)
Total: 14 (HIGH: 11, CRITICAL: 3)
┌────────────┬────────────────┬──────────┬────────────┬───────────────┬──────────────────────┐
│ Library │ Vulnerability │ Severity │ Status │ Installed Ver │ Fixed Version │
├────────────┼────────────────┼──────────┼────────────┼───────────────┼──────────────────────┤
│ libssl3 │ CVE-2024-6119 │ HIGH │ fixed │ 3.0.11-1 │ 3.0.13-1~deb12u1 │
│ zlib1g │ CVE-2023-45853 │ CRITICAL │ affected │ 1:1.2.13-1 │ │
│ libcurl4 │ CVE-2024-2004 │ HIGH │ fixed │ 7.88.1-10 │ 7.88.1-10+deb12u5 │
└────────────┴────────────────┴──────────┴────────────┴───────────────┴──────────────────────┘
Note the Status column. fixed means an upgrade exists; affected means the CVE is real but no patched version has shipped yet. That distinction drives triage.
Also open source, and built around SBOMs. Grype pairs with Syft (Anchore's SBOM generator): you produce a software bill of materials once, then scan it repeatedly without re-cracking the image. If you already generate SBOMs for supply-chain security with SBOMs, Grype fits that flow naturally. Detection quality is close to Trivy's for OS and language packages, though it does less on misconfig and secrets.
Commercial, and the most developer-focused of the three. Snyk's edge is fix advice: it tells you the exact base-image bump or dependency upgrade that clears the most CVEs, and it integrates into IDEs, PRs, and its own dashboard. You pay for that polish, plus curated vulnerability data that sometimes flags issues before public databases catch up. For teams that want managed data and guided remediation rather than raw findings, it earns its price.
Docker Scout: built into Docker Desktop and Docker Hub, convenient if you live in that ecosystem, with decent base-image recommendations.
Clair: the older open-source engine behind several registries; powerful but heavier to operate standalone.
Registry-native scanning: ECR, GCR/Artifact Registry, and ACR all scan images on push automatically. A great safety net because it runs whether or not your CI did, but a backstop, not a substitute for shifting the check left into the build.
Scanning is only useful if it changes what ships. The pattern:
Fail the build on HIGH/CRITICAL. Run the scan in CI and exit non-zero when severe, fixable findings appear. Here is a GitHub Actions step using Trivy:
- name: Scan image for vulnerabilities
uses: aquasecurity/trivy-action@0.24.0
with:
image-ref: myapp:${{ github.sha }}
format: table
exit-code: '1' # fail the job on a match
severity: HIGH,CRITICAL
ignore-unfixed: true # only block on CVEs that have a fix
ignore-unfixed: true is the pragmatic setting. Blocking on a CRITICAL with no available patch just stops your pipeline over something you cannot act on. Track those separately.
Scan and pin the base image. Pin by digest, not a floating tag, and rebuild on a schedule so base-image patches actually reach production.
Shrink the surface. Distroless or minimal bases (gcr.io/distroless, alpine, chainguard) remove the shells, package managers, and libraries you never use. Fewer packages means fewer CVEs by definition, and it is the single highest-leverage change most teams can make.
Generate an SBOM. Produce one at build time so you can answer "are we affected by the next Log4Shell?" in minutes instead of days.
Admission control. Use an admission controller (Kyverno, OPA Gatekeeper, or your registry's signing policy) to block images that were never scanned or signed from running in the cluster.
Point a scanner at a real image and you will get dozens of findings. Most are not actionable, and treating them as equal is how teams learn to ignore the scanner entirely. Prioritize by three signals together:
The goal is a short queue of "severe, fixable, reachable" items your team actually clears, not a 400-row report nobody reads. This mindset lines up with the broader application security best practices: reduce input, reduce surface, and act on what is exploitable rather than what merely exists.
Start with Trivy in CI, failing the build on HIGH/CRITICAL with ignore-unfixed on, and pair it with distroless base images to cut the finding count at the source. Turn on your registry's native scanning as a second layer, and add admission control so nothing unscanned reaches the cluster. If your organization can fund it and remediation speed matters more than tooling cost, add Snyk on top for its fix advice and reachability data. The tool choice matters less than the discipline around it: scan continuously, fail on what is fixable, shrink the image, and keep the queue short enough that people trust it.
Get the latest tutorials, guides, and insights on AI, DevOps, Cloud, and Infrastructure delivered directly to your inbox.
Explore more articles in this category
A service mesh solves real problems and creates new ones. This is the map: what it actually does, when it earns its cost, and how the options compare.
Your pipeline holds the keys to production and signs off on everything you ship, so harden both the pipeline itself and the artifacts it builds.
A practical, layer-by-layer checklist for securing Kubernetes clusters, workloads, networking, secrets, and the software supply chain.