Trivy: Container and IaC Vulnerability Scanning
Every container you ship has a bill of materials: the OS packages, language libraries, and binaries baked into the image. Most of them have vulnerabilities. Your job is to know which ones, triage by severity and fix availability, and not block shipping over a LOW-severity CVE in a library you don’t actually call.
Trivy makes that practical. It’s a single binary that scans container images, filesystems, git repositories, Terraform configs, Kubernetes manifests, running clusters, and generates SBOMs — all without a database server or account signup. Install it, point it at something, get results in seconds.
This guide covers the full workflow: image scanning, IaC misconfiguration detection, secret detection, Kubernetes cluster scanning, the Trivy Operator for continuous cluster monitoring, CI/CD integration, and how to configure ignore rules so your builds don’t fail on noise.
What Trivy Scans
Trivy has six scanner types, all available in the same binary:
| Scanner | What it finds |
|---|---|
vuln |
CVEs in OS packages and language dependencies |
misconfig |
Security misconfigurations in IaC and Kubernetes |
secret |
Hardcoded credentials, API keys, private keys |
license |
Dependency licenses (GPL, MIT, Apache, etc.) |
sbom |
Generates or consumes Software Bills of Materials |
And it targets multiple input types: container images, local filesystems, git repos, Kubernetes clusters, VM images, and SBOMs.
Vulnerability data comes from: OSV (Open Source Vulnerabilities), NVD (National Vulnerability Database), GitHub Advisory Database, vendor-specific OS advisories (Debian, Ubuntu, Alpine, RHEL, Amazon Linux), and language ecosystem databases (PyPI, npm, RubyGems, Maven, Go module proxy, Cargo, NuGet).
Installation
|
|
Verify:
|
|
Container Image Scanning
Basic scan
|
|
Default output shows a table grouped by target (OS packages, then each language ecosystem found):
nginx:latest (debian 12.9)
==========================
Total: 148 (UNKNOWN: 0, LOW: 105, MEDIUM: 29, HIGH: 12, CRITICAL: 2)
┌──────────────┬───────────────┬──────────┬──────────────────┬───────────────┬────────────────────────────────┐
│ Library │ Vulnerability │ Severity │ Installed Version│ Fixed Version │ Title │
├──────────────┼───────────────┼──────────┼──────────────────┼───────────────┼────────────────────────────────┤
│ openssl │ CVE-2024-0727 │ CRITICAL │ 3.0.11-1~deb12u2 │ 3.0.13-1~deb │ OpenSSL: denial of service │
│ zlib1g │ CVE-2023-45853│ CRITICAL │ 1:1.2.13.dfsg-1 │ │ zlib: integer overflow │
└──────────────┴───────────────┴──────────┴──────────────────┴───────────────┴────────────────────────────────┘
Filtering to what matters
In practice, most images have dozens to hundreds of LOW/MEDIUM findings, many without fixes. Filter immediately:
|
|
Output formats
|
|
Scanning private registries
|
|
Trivy reads Docker’s credential store after docker login, so authentication flows the same as docker pull.
Filesystem and Repository Scanning
Scan a local directory for vulnerabilities in dependency files (package.json, requirements.txt, go.sum, Gemfile.lock, pom.xml, etc.):
|
|
Skipping directories and files
|
|
Or configure in trivy.yaml (place in project root or ~/.trivy.yaml):
|
|
IaC and Misconfiguration Scanning
Trivy detects security misconfigurations in Terraform, Helm charts, Kubernetes manifests, and Dockerfiles using the config or fs --scanners misconfig mode.
Terraform
|
|
Example findings:
main.tf (terraform)
===================
Tests: 18 (SUCCESSES: 14, FAILURES: 4, EXCEPTIONS: 0)
Failures: 4 (UNKNOWN: 0, LOW: 1, MEDIUM: 1, HIGH: 1, CRITICAL: 1)
CRITICAL: Security group rule allows ingress from 0.0.0.0/0 on port 22
AVD-AWS-0107
─────────────────────────────────────────────────────────────────────
25 │ cidr_blocks = ["0.0.0.0/0"] ← line flagged
HIGH: S3 bucket has public ACL set
AVD-AWS-0176
─────────────────────────────────────────────────────────────────────
Kubernetes manifests
|
|
Common findings in Kubernetes manifests:
- Container running as root (
runAsNonRoot: falseor missing) allowPrivilegeEscalation: true- Missing
readOnlyRootFilesystem - Missing CPU/memory limits
- Using
hostNetwork: trueorhostPID: true - Privileged containers
- Missing liveness/readiness probes
Dockerfiles
|
|
Dockerfile findings:
Dockerfile (dockerfile)
=======================
Tests: 23 (SUCCESSES: 18, FAILURES: 5)
HIGH: Last USER command in Dockerfile is 'root'
DKR001
Ensure the last USER directive is not 'root'.
MEDIUM: ADD command used instead of COPY
DKR002
LOW: No HEALTHCHECK instruction in Dockerfile
DKR006
Secret Detection
Trivy scans for hardcoded credentials across all scan types. It’s enabled by default for image and filesystem scans.
|
|
Trivy detects:
- AWS access keys and secret keys
- GCP service account JSON keys
- GitHub/GitLab personal access tokens
- Slack tokens and webhook URLs
- Private keys (RSA, EC, DSA, OpenSSH)
- Generic high-entropy strings matching credential patterns
- Database connection strings with embedded passwords
- Azure storage account keys
Custom secret rules
|
|
|
|
License Scanning
Identify which open-source licenses are in your dependency tree — useful for legal review and catching unexpected copyleft licenses:
|
|
Output groups licenses by category:
my-app:latest (license)
=======================
┌──────────────────┬──────────────────┬───────────────┐
│ Package │ License │ Category │
├──────────────────┼──────────────────┼───────────────┤
│ django │ BSD-3-Clause │ Permissive │
│ psycopg2 │ LGPL-3.0 │ Weak Copyleft │
│ some-lib │ GPL-3.0 │ Restricted │ ← flagged
└──────────────────┴──────────────────┴───────────────┘
License categories:
- Permissive — MIT, Apache-2.0, BSD: generally safe for commercial use
- Weak Copyleft — LGPL, MPL-2.0: linking usually fine, modifications require sharing
- Restricted — GPL-2.0, GPL-3.0, AGPL-3.0: strong copyleft, may require open-sourcing your code
Ignoring Findings
.trivyignore (simple format)
# Ignore a CVE globally
CVE-2019-14697
# Ignore with expiry (auto-reinstates after date)
CVE-2021-12345 exp:2026-06-01
# Comment for context
CVE-2020-99999 exp:2026-12-31
.trivyignore.yaml (full format — recommended)
|
|
|
|
Kubernetes Cluster Scanning
Trivy can scan a live cluster — checking running workload images for CVEs and cluster resources for misconfigurations:
|
|
trivy k8s combines image vulnerability scanning (from the registry) with configuration auditing (from the live manifests). It reports:
- CVEs in images running in your cluster
- Containers running as root
- Missing resource limits
- Privileged pods
- Exposed secrets in ConfigMaps or environment variables
- RBAC misconfigurations
SBOM Generation
Generate a Software Bill of Materials for compliance, supply chain security, and vulnerability tracking:
|
|
The SBOM workflow:
- Generate SBOM at build time and attach to the container image (via
cosign attach sbom) - Consumers can scan the SBOM with
trivy sbomwithout pulling the full image - Re-scan SBOMs when new CVEs are disclosed without rebuilding
CI/CD Integration
GitHub Actions
|
|
Results appear in the repository’s Security → Code scanning tab, linked to the specific commit that introduced each vulnerability.
Scanning IaC in pull requests
|
|
GitLab CI
|
|
Exit codes
| Exit code | Meaning |
|---|---|
0 |
No vulnerabilities found at the specified severity |
1 |
Vulnerabilities found |
2 |
Error (misconfiguration, scan failure) |
Use --exit-code 0 to collect results without failing the build (for reporting-only pipelines):
|
|
Trivy Server Mode
In environments where many machines scan images, running each with its own local DB copy means every machine re-downloads the same ~200 MB vulnerability database. Server mode centralizes the DB:
|
|
Kubernetes deployment
|
|
Clients in the cluster:
|
|
Trivy Operator: Continuous Kubernetes Scanning
The Trivy Operator watches your cluster and automatically scans every workload, storing results as Kubernetes custom resources. You get always-current vulnerability reports without running scans manually.
Installation
|
|
What gets created automatically
Deploy anything and the operator scans it within minutes:
|
|
CRDs
VulnerabilityReport — CVEs in container images:
|
|
ConfigAuditReport — Kubernetes misconfigurations:
|
|
Prometheus metrics
The operator exposes Prometheus metrics for all reports:
|
|
Key PromQL queries:
|
|
Import Grafana dashboard ID 16337 (Trivy Operator Dashboard) for pre-built visualizations.
Comparison with Alternatives
| Tool | Type | Strengths | Weaknesses |
|---|---|---|---|
| Trivy | All-in-one | Multi-target, IaC, secrets, SBOM, K8s operator, no account needed | Less precision on some ecosystems vs specialized tools |
| Grype | Vuln-only | High accuracy, EPSS+KEV risk scoring, fast | No IaC, secret, or license scanning |
| Snyk | Platform | Fix PRs, developer IDE integration, SAST | Paid for serious use, account required, rate limits on free tier |
| Clair | Registry-native | Continuous scanning as images are pushed | Complex setup, requires separate service, API-driven |
| Docker Scout | Docker-native | Zero setup for Docker Hub users, integrated into docker CLI |
Weaker IaC/cluster scanning, some features paid |
When to use Trivy: You want one tool for images, IaC, cluster scanning, and SBOMs. You don’t want to manage accounts or rate limits. You need air-gap support.
When to complement with Grype: Your team needs more precise risk scoring (EPSS probability + KEV known-exploited weighting) to prioritize which CVEs to fix first.
Practical Workflow
A real shift-left security workflow using Trivy at every stage:
Developer workstation:
trivy image --severity HIGH,CRITICAL --ignore-unfixed my-app:dev
trivy config ./k8s/ ./terraform/
trivy fs --scanners secret .
Pull request (GitHub Actions):
trivy-action → SARIF → GitHub Security tab
trivy config → SARIF → GitHub Security tab
Container registry (on push to main):
trivy image --format cyclonedx --output sbom.cdx.json → attach to image
trivy image --severity CRITICAL --exit-code 1 → gate the push
Production cluster (continuous):
trivy-operator → VulnerabilityReport + ConfigAuditReport CRDs
ServiceMonitor → Prometheus → Grafana dashboard
Alert: sum(trivy_image_vulnerabilities{severity="CRITICAL",namespace="production"}) > 0
The goal isn’t zero findings — that’s impossible with any real image. It’s a defined, enforced threshold: no CRITICAL unfixed in production, HIGH must be triaged within 7 days, dashboard shows the trend going down.
Summary
Trivy replaces a collection of specialized scanners with a single binary:
| Task | Command |
|---|---|
| Scan image for CVEs | trivy image --severity HIGH,CRITICAL --ignore-unfixed my-app:latest |
| Scan Terraform | trivy config ./terraform/ |
| Scan Kubernetes manifests | trivy config ./k8s/ |
| Scan for secrets | trivy fs --scanners secret . |
| Generate SBOM | trivy image --format cyclonedx -o sbom.cdx.json my-app:latest |
| Scan live cluster | trivy k8s cluster |
| Continuous cluster monitoring | helm install trivy-operator aquasecurity/trivy-operator |
| CI gate on CRITICAL | trivy image --severity CRITICAL --exit-code 1 my-app:latest |
The Trivy Operator is what makes this sustainable at scale — instead of running scans manually or on a schedule, every workload in the cluster is continuously monitored and results are surfaced as Kubernetes resources you can query, alert on, and track over time.
Start with trivy image on your most critical services. Add the operator to your cluster. Wire up the Prometheus metrics. From there, the workflow drives itself.
Comments