Vulnerability Basics
This page is a hands-on intro to scanning container images with Trivy and Grype, reading their output, and wiring severity thresholds into CI so bad images fail the build.
You build images with Docker, but remember that containerd via the CRI runs the pods on your nodes. Scanning happens against the image artifact, so it protects both worlds equally.
Prerequisites
- Docker Engine 29.6.1 to build the images you will scan.
- Trivy (Aqua Security) - a scanner, SBOM generator, and misconfig checker.
- Grype (Anchore) - a second scanner that pairs with the
syftSBOM tool. - A CI runner (GitHub Actions, GitLab CI, or similar) that can run containers.
Quick install on Debian/Ubuntu:
# Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin
# Grype
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sudo sh -s -- -b /usr/local/binBasic Examples
1. Scan an image with Trivy
Run Trivy against any local or remote image reference.
trivy image myapp:1.4.0- Trivy pulls the image if it is not local, then reads its package metadata.
- It matches installed package versions against its vulnerability database.
- Output groups findings by severity:
LOW,MEDIUM,HIGH,CRITICAL. - The
UNKNOWNbucket holds findings with no scored severity yet. - First run downloads the DB; later runs reuse the cache.
2. Scan the same image with Grype
Grype gives you a second opinion from a different feed and matcher.
grype myapp:1.4.0- Two scanners disagree often, so running both reduces blind spots.
- Grype prints a table of package, installed version, fixed version, and severity.
- The
FIXED-INcolumn tells you whether an upgrade path exists. - Use one scanner as your gate and the other as a cross-check.
3. Show only fixable findings
Noise drops sharply when you hide vulnerabilities with no available fix.
trivy image --ignore-unfixed myapp:1.4.0--ignore-unfixedhides CVEs that have no patched version yet.- This focuses engineers on findings they can actually resolve today.
- Track the hidden unfixed set separately so it does not disappear entirely.
4. Fail the build above a severity threshold
The whole point of CI scanning is a non-zero exit code that stops the pipeline.
trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:1.4.0--exit-code 1makes Trivy return failure when matching findings exist.--severity HIGH,CRITICALlimits the gate to the severities you block on.- A passing scan exits
0, so the pipeline continues to publish. - Start strict on
CRITICALonly, then tighten toHIGHas you clean up. - Keep the gate close to the build so a failure is cheap and obvious.
5. Generate an SBOM
An SBOM is a package inventory you can scan later without the original image.
trivy image --format cyclonedx --output sbom.json myapp:1.4.0- CycloneDX and SPDX are the two common SBOM formats.
- Store the SBOM as a build artifact next to the image.
- Later you can
trivy sbom sbom.jsonto re-check against a fresh DB. - This answers "are we affected by the new CVE?" in seconds.
6. Scan a filesystem or repo, not just an image
Catch dependency CVEs before you even build the image.
trivy fs --scanners vuln,secret .trivy fsreads lockfiles likepackage-lock.jsonorgo.sum.- The
secretscanner also flags committed keys and tokens. - Running this in CI shifts detection left, closer to the developer.
7. Read a severity score
Severity comes from CVSS, a 0.0 to 10.0 scale.
CVE-2024-XXXXX openssl 3.0.11 fixed: 3.0.14 CRITICAL (9.8)0.1-3.9is Low,4.0-6.9Medium,7.0-8.9High,9.0-10.0Critical.- The score is worst-case impact, not proof you are exploitable.
- Pair the score with reachability before you panic over a raw count.
- A High CVE in an unreachable build tool often matters less than a Medium on your request path.
- Use severity to sort your queue, then use context to decide what to fix first.
Intermediate Examples
8. Gate a GitHub Actions build
Wire the scan into a pipeline so no one can merge a Critical image.
- name: Scan image
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: CRITICAL
exit-code: "1"
ignore-unfixed: "true"- The action fails the job when a fixable Critical CVE is present.
github.shaties the scan to the exact commit under test.- Upload the SARIF output to code scanning for a durable record.
- Keep the gate close to the build step so failures are cheap.
9. Suppress a specific finding with an expiry
Sometimes you must accept a CVE briefly while a fix lands upstream.
# .trivyignore.yaml
vulnerabilities:
- id: CVE-2024-12345
expired_at: 2026-08-01
statement: "No upstream fix; tracked in TICKET-482"expired_atmakes the waiver self-destruct, so it cannot rot forever.statementrecords why, which auditors and teammates will ask for.- This is the lightweight form of the Exception Process page.
10. Fail only on new findings
Large legacy images have a backlog. Block regressions, not history.
trivy image --exit-code 1 --severity CRITICAL --ignore-unfixed myapp:1.4.0- Combining
--ignore-unfixedwith aCRITICALgate blocks only actionable, severe, fixable issues. - This lets a team adopt scanning without a wall of red on day one.
- Ratchet the threshold down as the backlog shrinks over sprints.
- Record the unfixed backlog in a report so it is tracked, not forgotten.
- Once the backlog is clean, extend the gate to
HIGHfor stronger coverage.
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).