32.2 Vulnerability Scanning: Trivy, Grype, and Snyk
Right, let’s talk about scanning your container images for vulnerabilities. This isn’t a “nice-to-have”; it’s your digital immune system. You’re constantly pulling in code from strangers on the internet (yes, that FROM node:18 base image counts), and you need to know if they’ve given you the software equivalent of a cold. We’re going to look at three top scanners: Trivy, Grype, and Snyk. My job is to make you understand not just how to run them, but why you’d pick one and how to actually use the results without losing your mind.
The Core Concept: It’s All About the Database
All these scanners work on the same fundamental principle: they unpack your container image, inventory all the packages (deb, rpm, apk, npm, pip, you name it), and then scream into the void by comparing them against a giant database of known vulnerabilities (CVEs). The magic, and the difference between tools, is in the quality, freshness, and breadth of that database. A scanner is only as good as its data. If its database is a week out of date, it’s about as useful as a chocolate teapot.
Trivy: The Reliable Workhorse
Aqua Security’s Trivy is, in my professional opinion, the default choice for most people. It’s stupidly fast, open-source, and comprehensive. It doesn’t require a daemon or any pre-setup, which is a godsend for CI/CD. You just run the binary. Its database is built from a ton of sources (including the excellent OSV project), and it scans for OS and language-level packages out of the box.
Here’s the classic way to run it. The --severity flag is your best friend; without it, you’ll get a firehose of mostly irrelevant LOW and MEDIUM CVEs that will numb your brain.
# Scan an image in a remote registry
trivy image --severity CRITICAL,HIGH your-registry/your-app:latest
# Scan a local tarball (useful if you build offline)
docker save your-app:latest -o app.tar
trivy image --input app.tar
# Exit with an error code if any CRITICAL vulns are found. This is your CI gate.
trivy image --exit-code 1 --severity CRITICAL,HIGH your-registry/your-app:latest
Its biggest strength is also a minor weakness: it’s almost too comprehensive. You’ll need to learn to filter its output aggressively.
Grype: The Understudy with Potential
Grype is Anchore’s open-source offering. It’s conceptually identical to Trivy but often lags slightly behind in database freshness and speed in my ad-hoc testing. It’s a solid scanner, but it feels like it’s always playing catch-up. Where Grype starts to get interesting is if you’re already bought into the full Anchore ecosystem (like their Enterprise platform). On its own, it’s a good, but not best-in-class, tool.
# Basic scan. Note the different format for severity filtering.
grype your-registry/your-app:latest --only-fixed
# Output in SARIF format for ingesting into GitHub Advanced Security
grype your-registry/your-app:latest -o sarif
The --only-fixed flag is a killer feature here. It only shows you vulnerabilities that have an available fix. This cuts through the noise and tells you what you can actually action. Why every scanner doesn’t have this option front-and-center is a mystery to me.
Snyk: The Polished Powerhouse (with a Price Tag)
Snyk is brilliant. Its database, especially for application-level dependencies (Node.js, Python, etc.), is arguably the best in the business. It’s terrifyingly good. The CLI tool is slick and its output is incredibly developer-friendly, often providing paths to upgrade and clear explanations. It also integrates deeply with their SaaS platform for monitoring and management.
But—and it’s a big but—the free tier is limited. You get a small number of tests per month. For serious use, you pay. And you pay a lot.
# First, authenticate with your Snyk account (free or paid)
snyk auth
# Test a container image
snyk container test your-registry/your-app:latest
# Monitor and record the results in the Snyk dashboard
snyk container monitor your-registry/your-app:latest
Use Snyk if you have the budget and want the best possible visibility, especially into your application dependencies. Use Trivy if you need a free, powerful, and fast engine to bolt into your pipeline today.
The One Unforgivable Sin: Scanning the Wrong Thing
Listen carefully. This is the most common, most devastating mistake. You MUST scan the image that is actually pushed to your registry, NOT the local image tag right after you build it.
Why? Because your Docker daemon might be lying to you. Those image tags are mutable. What you built as my-app:latest might be different from what’s in the registry due to a subsequent build or a promotion. Your CI pipeline must be structured like this:
# 1. Build the image
docker build -t my-registry.com/my-team/my-app:${CI_COMMIT_SHA} .
# 2. Push the image
docker push my-registry.com/my-team/my-app:${CI_COMMIT_SHA}
# 3. Scan the SPECIFIC, IMMUTABLE image you just pushed
trivy image --exit-code 1 --severity CRITICAL my-registry.com/my-team/my-app:${CI_COMMIT_SHA}
Scanning the immutable SHA-tagged image is the only way to be sure you’re evaluating what’s actually going to be deployed.
Taming the Output: Ignoring False Positives
You will get false positives. You will get vulnerabilities in packages that are never actually loaded in your runtime. You will get things that are deemed “unfixable” by the distro maintainers. You can’t just fix everything. The key is to create and maintain an ignore policy file (a .trivyignore or similar) that silences known non-issues so your team doesn’t get alert fatigue.
# .trivyignore example
# This is a false positive in a base image we can't change right now
CVE-2020-12345
# This vulnerability is in a dev tool we don't ship in the final image
CVE-2021-67890
Review this file religiously. It should be a living document in your codebase, not a secret file on your laptop. This is how you move from a screaming red dashboard to a actionable security posture.