Trivy: Container Image and IaC Security Scanning
Trivy is an open-source vulnerability scanner that covers container images (OS packages and application dependencies), Kubernetes manifests, Terraform, Dockerfiles, and secrets. It's fast, requires no setup beyond installation, and produces structured JSON or table output. Use Trivy in CI to block builds with critical vulnerabilities before they reach production.
Key Takeaways
Trivy scans container images without running them. It pulls the image layers, inventories all installed packages and application libraries, and checks them against vulnerability databases (CVE, OSV, GitHub Advisory Database).
Trivy covers more than just OS vulnerabilities. It also finds vulnerable npm, pip, gem, Go, and Maven packages—the application dependency layer that many image scanners miss.
Trivy finds secrets in images. It scans file contents in image layers for API keys, tokens, and other secrets accidentally included in container images.
Trivy scans Terraform and Kubernetes IaC. Misconfigurations in infrastructure code (overly permissive IAM, missing network policies, privileged containers) are found before deployment.
Use --exit-code 1 to fail CI on critical vulnerabilities. By default Trivy reports findings but exits 0. Add --exit-code 1 --severity CRITICAL,HIGH to block CI pipelines on high-severity findings.
Installation
# macOS
brew install trivy
<span class="hljs-comment"># Linux (Debian/Ubuntu)
<span class="hljs-built_in">sudo apt-get install wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key <span class="hljs-pipe">| <span class="hljs-built_in">sudo apt-key add -
<span class="hljs-built_in">echo deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main <span class="hljs-pipe">| <span class="hljs-built_in">sudo <span class="hljs-built_in">tee -a /etc/apt/sources.list.d/trivy.list
<span class="hljs-built_in">sudo apt-get update && <span class="hljs-built_in">sudo apt-get install trivy
<span class="hljs-comment"># Docker (no local install required)
docker run aquasec/trivy image nginx:latestScanning Container Images
Basic Scan
# Scan a public image
trivy image nginx:latest
<span class="hljs-comment"># Scan a specific image tag
trivy image python:3.11-slim
<span class="hljs-comment"># Scan a private image (authenticates using Docker credentials)
trivy image myregistry.example.com/myapp:1.2.3Output shows:
- Vulnerability ID (CVE number)
- Package name and installed version
- Fixed version (if a patch is available)
- Severity (CRITICAL, HIGH, MEDIUM, LOW)
- Description
Filter by Severity
# Only show CRITICAL and HIGH
trivy image --severity CRITICAL,HIGH nginx:latest
<span class="hljs-comment"># Fail CI if CRITICAL vulnerabilities found
trivy image --exit-code 1 --severity CRITICAL nginx:latestScan a Local Image
# Build first, then scan
docker build -t myapp:<span class="hljs-built_in">test .
trivy image myapp:<span class="hljs-built_in">test
<span class="hljs-comment"># Or scan from a tar file
docker save myapp:<span class="hljs-built_in">test -o myapp.tar
trivy image --input myapp.tarOutput Formats
# Default table output
trivy image nginx:latest
<span class="hljs-comment"># JSON output (for CI processing)
trivy image --format json --output trivy-results.json nginx:latest
<span class="hljs-comment"># SARIF output (for GitHub Code Scanning)
trivy image --format sarif --output trivy.sarif nginx:latest
<span class="hljs-comment"># Template-based HTML report
trivy image --format template --template <span class="hljs-string">"@contrib/html.tpl" --output report.html nginx:latestScanning Application Dependencies
Trivy scans application dependency files directly (without a container image):
# Scan current directory for vulnerable dependencies
trivy fs .
<span class="hljs-comment"># Scan specific manifest files
trivy fs package-lock.json
trivy fs requirements.txt
trivy fs go.sum
trivy fs Gemfile.lock
trivy fs pom.xmlSupported ecosystems:
- npm (package-lock.json, yarn.lock)
- pip (requirements.txt, Pipfile.lock, poetry.lock)
- Go modules (go.sum, go.mod)
- Ruby (Gemfile.lock)
- Maven (pom.xml)
- Gradle (gradle.lockfile)
- Rust (Cargo.lock)
- PHP (composer.lock)
Scanning Infrastructure as Code
Dockerfile Scanning
# Scan a Dockerfile for security misconfigurations
trivy config DockerfileCommon findings:
- Running as root user (
USER rootnot followed by a non-root USER instruction) - Missing
HEALTHCHECKinstruction ADDfrom URL (use COPY instead for better reproducibility)- Sensitive data exposed via
ARG(visible in image history) - Latest tag used (non-deterministic builds)
Example Dockerfile with issues:
FROM ubuntu:latest # ← Should pin a specific version
ARG API_KEY # ← ARG values appear in image history
ENV API_KEY=$API_KEY # ← Secret in environment variable
RUN apt-get install curl # ← Missing -y flag, and no apt-get update
COPY . /app
CMD ["python", "app.py"] # ← Running as rootTerraform Scanning
# Scan Terraform directory
trivy config ./terraform/
<span class="hljs-comment"># Scan with Terraform plan output
terraform plan -out=tfplan
trivy config tfplanCommon Terraform findings:
- S3 bucket with public ACL
- Security groups with
0.0.0.0/0ingress on port 22 or 3389 - IAM policies with
*actions or resources - CloudTrail logging disabled
- EBS volumes not encrypted
- RDS instances without encryption at rest
Kubernetes Manifest Scanning
# Scan Kubernetes YAML files
trivy config k8s/
<span class="hljs-comment"># Scan a specific manifest
trivy config deployment.yamlCommon Kubernetes findings:
- Containers running as root (missing
runAsNonRoot: true) - Privileged containers (
privileged: true) - Containers with read-write root filesystem (missing
readOnlyRootFilesystem: true) - Missing resource limits
- Missing network policies
- Containers with host path mounts
Secret Scanning in Images
Trivy can find secrets in container image layers—credentials accidentally committed and built into images:
trivy image --scanners secret nginx:latestEnable secret scanning alongside vulnerability scanning:
trivy image --scanners vuln,secret myapp:latestCommon secret types detected:
- AWS access keys and secret keys
- GitHub tokens and PATs
- Generic API keys and tokens
- SSH private keys
- Database connection strings with passwords
- JWT secrets
Setting Vulnerability Ignore Policies
Some vulnerabilities have no fix (no patched version exists) or are not applicable to your use case. Use a .trivyignore file:
# .trivyignore
# CVE-2023-12345 - Not applicable: we don't use the vulnerable code path
CVE-2023-12345
# CVE-2023-67890 - No fix available, accepted risk per security team 2025-01-15
CVE-2023-67890Or use an ignore policy file with expiry dates:
# trivy-ignore.yaml
ignoredVulnerabilities:
- id: CVE-2023-12345
statement: "Not applicable: we don't use Python's email module"
expiredAt: "2025-12-31"
- id: CVE-2023-67890
statement: "No fix available, accepted risk approved by security team"
expiredAt: "2025-06-01"trivy image --ignorefile trivy-ignore.yaml myapp:latestCI/CD Integration
GitHub Actions
# .github/workflows/trivy.yml
name: Container Security Scan
on:
push:
branches: [main]
pull_request:
jobs:
trivy-image:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy vulnerability scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: sarif
output: trivy-results.sarif
exit-code: '1'
severity: 'CRITICAL,HIGH'
- name: Upload to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-results.sarif
trivy-config:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy IaC scan
uses: aquasecurity/trivy-action@master
with:
scan-type: config
scan-ref: .
exit-code: '1'
severity: 'CRITICAL,HIGH'GitLab CI
# .gitlab-ci.yml
trivy-scan:
stage: test
image:
name: aquasec/trivy:latest
entrypoint: [""]
script:
- trivy image --exit-code 1 --severity CRITICAL,HIGH $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
artifacts:
when: always
reports:
container_scanning: trivy-results.jsonUsing Trivy in Docker Compose for Local Testing
# docker-compose.test.yml
services:
trivy:
image: aquasec/trivy:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./.trivyignore:/root/.trivyignore
command: >
image
--severity CRITICAL,HIGH
--exit-code 1
myapp:testRun as part of local testing:
docker compose -f docker-compose.test.yml run trivyPractical Tips
Use distroless or minimal base images. alpine, distroless/python, distroless/java have far fewer vulnerabilities than ubuntu or debian because they have fewer installed packages. Fewer packages = smaller attack surface.
Pin base image digests in production. FROM nginx@sha256:abc123 guarantees reproducible builds and prevents supply chain attacks via tag mutation.
Scan early, scan often. Vulnerabilities in dependencies appear over time. A dependency that was safe when you added it may have a CVE six months later. Schedule daily or weekly scans of production images.
Fix by upgrading the base image first. Most OS-level vulnerabilities are fixed by updating to a newer base image version. FROM python:3.11.8-slim → FROM python:3.11.9-slim fixes many CVEs automatically.
Use multi-stage builds to reduce the attack surface. Only include what's needed at runtime. Build dependencies (compilers, dev tools) shouldn't appear in the final image.
Comparing Base Images
Use Trivy to compare vulnerability counts across base images:
trivy image --severity CRITICAL,HIGH python:3.11-bullseye 2>&1 | grep <span class="hljs-string">"Total:"
trivy image --severity CRITICAL,HIGH python:3.11-slim-bullseye 2>&1 <span class="hljs-pipe">| grep <span class="hljs-string">"Total:"
trivy image --severity CRITICAL,HIGH python:3.11-alpine 2>&1 <span class="hljs-pipe">| grep <span class="hljs-string">"Total:"This quickly shows which base image has the smallest vulnerability footprint.
Summary
Trivy is a comprehensive security scanner for the container and IaC ecosystem:
- Container images: OS packages and application dependencies across all major ecosystems
- Filesystems: scan dependency manifests without building an image
- IaC: Terraform, Kubernetes, Dockerfile misconfigurations
- Secrets: credentials accidentally included in image layers
Key CI integration points:
- Build your container image in CI
- Run
trivy image --exit-code 1 --severity CRITICAL,HIGHto block on critical findings - Upload SARIF to GitHub for review in pull requests
- Run
trivy config .to catch IaC misconfigurations before deployment