Image and Filesystem Scanning
.mdVerified against Trivy 0.68.2, flags verified via `trivy --help` / `trivy image --help` / `trivy fs --help` · official docs
What it is and where it fits#
Trivy (by Aqua Security) is the most widely used open-source container scanner — genuinely broad in scope: container images, filesystems, git repositories, IaC/config misconfigurations, and SBOMs, all from one static binary with no daemon required. It overlaps deliberately with several other tools in this series: it does what Grype does (image/fs vulnerability scanning), what tfsec/Checkov do (IaC misconfiguration scanning, and as of 2024 it literally absorbed tfsec's check library), and what Syft does (SBOM generation) — which is exactly why many teams standardize on Trivy alone rather than running four separate scanners. This page covers vulnerability scanning of images/filesystems/repos; the companion page covers config/IaC scanning and SBOM handling.
How Trivy's image scan actually works#
The vulnerability DB is downloaded and cached locally on first run (~/.cache/trivy) and refreshed
automatically on subsequent scans unless --skip-db-update is set — this is why the very first trivy image
run on a fresh machine is noticeably slower than every run after it.
Installation#
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
# add -- <version> (e.g. v0.68.2) to pin instead of installing latest
brew install trivy # macOS/Linux Homebrew
apt-get install trivy # if you've added Aqua's official apt repo (see docs)
trivy --version
trivy image --download-db-only # pre-warm the vulnerability DB (large first download)Trivy shipped two supply-chain incidents in 2026 (a compromised release, and a compromised GitHub Action) —
always pin an install script to a specific --version/tag in CI rather than trusting "latest" unattended, and
watch Aqua's security advisories.
Scanning a container image#
trivy image python:3.4-alpine
trivy image --severity CRITICAL,HIGH myapp:latest
trivy image --exit-code 1 --severity CRITICAL,HIGH myapp:latest # non-zero exit for CI gating (0 by default!)
trivy image --ignore-unfixed myapp:latest # hide vulns with no available fix yet
trivy image --input myapp.tar # scan a `docker save` tarball, no daemon needed
trivy image --format json --output result.json myapp:latest
trivy image --format cyclonedx --output result.cdx myapp:latest # SBOM-shaped output, straight from an image scan--exit-code defaults to 0 — Trivy prints findings but won't fail a pipeline unless you set it explicitly.
This is the single most common "why didn't CI catch this" gotcha with Trivy, and it's shared by Grype too.
Sample trivy image table output (real shape — the exact CVE list depends entirely on the image scanned and
the DB's state on the day you scan; this is illustrative, not a literal fixed capture):
python:3.4-alpine (alpine 3.9.4)
==================================
Total: 42 (UNKNOWN: 0, LOW: 3, MEDIUM: 18, HIGH: 15, CRITICAL: 6)
┌─────────────┬────────────────┬──────────┬───────────────────┬───────────────┬──────────────────────────┐
│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │
├─────────────┼────────────────┼──────────┼───────────────────┼───────────────┼──────────────────────────┤
│ musl │ CVE-2020-28928 │ MEDIUM │ 1.1.20-r4 │ 1.1.20-r5 │ musl libc through 1.2.1 │
│ openssl │ CVE-2021-3711 │ CRITICAL │ 1.1.1b-r1 │ 1.1.1k-r0 │ OpenSSL: SM2 decryption │
└─────────────┴────────────────┴──────────┴───────────────────┴───────────────┴──────────────────────────┘
Scanning a filesystem or repository#
trivy fs . # local project — language-specific lockfiles + installed OS packages
trivy fs ./Pipfile.lock # a single manifest file
trivy repository https://github.com/org/repo # clone + scan a remote git repo directly
trivy repository /path/to/local/repotrivy fs is the tool for "what's vulnerable in my dependencies before I've even built an image" — the same
job snyk test or npm audit do, but offline and covering many ecosystems from one binary (npm, pip, Go
modules, Maven, Cargo, and more, auto-detected from lockfiles present).
Filtering and scan scope#
trivy image --scanners vuln myapp:latest # vuln only (default is vuln+secret)
trivy image --scanners vuln,secret,misconfig,license myapp:latest
trivy image --pkg-types os myapp:latest # OS packages only, skip language deps
trivy image --vuln-severity-source ghsa myapp:latest # pick which advisory DB drives severity when sources disagree
trivy image --platform linux/arm64 myapp:latest # scan a specific platform from a multi-arch imageReal-world scenario: baseline a legacy image without drowning in noise#
A container that's been in production for years typically has dozens of pre-existing HIGH/CRITICAL findings with no available fix. Gate new builds on newly introduced, actually-fixable issues instead of the entire backlog:
trivy image --exit-code 1 --severity CRITICAL,HIGH --ignore-unfixed myapp:latest--ignore-unfixed alone doesn't solve everything — it filters out vulnerabilities with genuinely no available
patch yet, but a large image will still show real, fixable HIGH/CRITICAL findings on day one. Pair this with a
.trivyignore (see the companion page) for specific accepted-risk exceptions the team has actually reviewed.
Real-world scenario: GitHub Actions CI recipe#
# .github/workflows/trivy.yml
name: Trivy Image Scan
on: [pull_request]
jobs:
trivy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: myapp:${{ github.sha }}
format: sarif
output: trivy-results.sarif
exit-code: '1'
severity: 'CRITICAL,HIGH'
ignore-unfixed: true
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-results.sarifif: always() on the upload step matters — without it, a failed scan (exit code 1, which is the whole point
of gating) skips the SARIF upload too, so the findings never actually reach GitHub's code-scanning UI for
review.
Shell completion#
trivy completion bash | sudo tee /etc/bash_completion.d/trivy
# or, per-shell session: source <(trivy completion bash)Common pitfalls#
- Forgetting
--exit-code 1— like Grype, Trivy's default exit code ignores findings entirely. - Treating the first scan's slowness as a hang — the first run on a machine downloads the full vulnerability DB; it's not stuck, just cold.
- Not pinning the install script version — see the supply-chain-incident note under Installation.
- Scanning
latestand assuming the results are stable — a mutable tag can point to a different image tomorrow; pin to a digest (myapp@sha256:...) in any report you intend to compare over time.
When to reach for something else#
If the team is already standardized on Grype+Syft (Anchore's pairing) for image scanning and SBOM generation specifically, there's little reason to add Trivy purely for image scanning — the two do materially the same job for that one use case. Trivy's advantage is breadth (one tool instead of three) once IaC and SBOM scanning are in scope too — see the companion page.