Container Vulnerability Scanning: Trivy vs Grype vs Snyk

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.

Why images pile up CVEs #

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.

What scanners actually check #

The better tools go well past "list the CVEs." A full scan covers:

OS packages: cross-reference installed package versions against vulnerability databases (NVD, distro security trackers, GitHub Advisories).

Language dependencies: parse lockfiles and installed modules for known-vulnerable versions.

Misconfiguration: Dockerfile and IaC checks (running as root, no USER directive, world-writable files).

Secrets: hardcoded API keys, tokens, and private keys baked into a layer.

Licenses: flag GPL or other licenses that violate your policy.

Not every tool does all five, and that spread is most of the difference between them.

The tools #

Trivy (Aqua Security) #

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:

bash.bash

$ 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.

Grype (Anchore) #

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.

Snyk #

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.

The rest #

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.

The workflow that actually works #

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:

yaml.yaml

- 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.

The noise problem #

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:

Severity: CRITICAL and HIGH first, but severity alone is a weak filter.

Fix available: a CVE with a patched version is work you can finish today. One without is a note to watch.

Reachability: is the vulnerable code path actually invoked? A CVE in a library your app links but never calls is far lower risk than one in your request-handling hot path. Tools with reachability analysis (Snyk, and increasingly others) cut the list dramatically.

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.

How to choose #

You want one free tool covering the most ground: Trivy. Fast, broad, no infrastructure.

You already build SBOMs and want scanning to slot into that pipeline: Grype plus Syft.

You want managed data, IDE/PR integration, and guided fixes, and can pay: Snyk.

You live in Docker's ecosystem and want zero setup: Docker Scout as a first pass.

Regardless of the above: turn on registry-native scanning as a backstop.

The call we'd make #

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.