Introduction
Containers ship fast — and they carry their vulnerabilities with them. A single FROM ubuntu:20.04 layer can contain dozens of known CVEs, and misconfigured Kubernetes manifests are a leading cause of cloud breaches. Without automated scanning, these issues only surface after an incident.
Trivy (pronounced "try-vee") is an open-source, all-in-one security scanner from Aqua Security. A single tool covers:
- OS package vulnerabilities — Alpine, Debian, Ubuntu, RHEL, Amazon Linux, and more
- Language ecosystem dependencies — npm, pip, Maven, Go modules, Cargo, Composer, NuGet
- Infrastructure as Code misconfigurations — Dockerfile, Kubernetes YAML, Terraform, CloudFormation, Helm
- Exposed secrets — API keys, tokens, certificates, database connection strings (150+ pattern types)
- License compliance — detect GPL/AGPL code in commercial projects
In this guide you'll go from a fresh Trivy install to a fully integrated scanning pipeline that catches issues before they reach production.
Prerequisites
Before starting, confirm you have:
- Docker Engine 20.10+ (
docker --version) - curl or a package manager for installation
- 4 GB free disk space for the Trivy vulnerability database
- A container image to practice with (we use public images throughout)
Step 1: Install Trivy
Option A — Install Script (Linux / macOS)
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \
| sh -s -- -b /usr/local/bin v0.58.0Verify:
trivy --version
# trivy version 0.58.0Option B — Package Manager
Debian / Ubuntu:
sudo apt-get install -y wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key \
| gpg --dearmor \
| sudo tee /usr/share/keyrings/trivy.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] \
https://aquasecurity.github.io/trivy-repo/deb \
$(lsb_release -sc) main" \
| sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt-get update && sudo apt-get install -y trivyRHEL / CentOS / Rocky Linux:
RELEASE=$(grep -Po '(?<=VERSION_ID=")[0-9]' /etc/os-release)
sudo tee /etc/yum.repos.d/trivy.repo << EOF
[trivy]
name=Trivy repository
baseurl=https://aquasecurity.github.io/trivy-repo/rpm/releases/$RELEASE/\$basearch/
gpgcheck=1
enabled=1
gpgkey=https://aquasecurity.github.io/trivy-repo/rpm/public.key
EOF
sudo yum -y install trivymacOS (Homebrew):
brew install trivyOption C — Docker (Zero Install)
Run Trivy as a container without installing anything locally:
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $HOME/.cache/trivy:/root/.cache/trivy \
aquasec/trivy:latest image nginx:latestMounting the Docker socket lets Trivy access locally built images. The cache volume prevents re-downloading the vulnerability database on every run.
Step 2: Your First Container Scan
Basic Image Scan
trivy image nginx:latestOn first run, Trivy downloads the vulnerability database (~200 MB). Subsequent runs use the local cache.
Output is grouped by layer/package and sorted by severity:
nginx:latest (debian 12.5)
==========================
Total: 142 (UNKNOWN: 0, LOW: 88, MEDIUM: 31, HIGH: 19, CRITICAL: 4)
┌──────────────────────┬────────────────┬──────────┬──────────────────────┬───────────────────┐
│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │
├──────────────────────┼────────────────┼──────────┼──────────────────────┼───────────────────┤
│ libssl3 │ CVE-2024-5535 │ CRITICAL │ 3.0.11-1~deb12u2 │ 3.0.13-1~deb12u1 │
│ openssl │ CVE-2024-5535 │ CRITICAL │ 3.0.11-1~deb12u2 │ 3.0.13-1~deb12u1 │
...
Filter to Critical and High Only
trivy image --severity CRITICAL,HIGH nginx:latestThis dramatically reduces noise, focusing your team on what matters most.
Compare Base Images
Alpine images carry far fewer OS vulnerabilities than Debian/Ubuntu:
# Debian-based — many findings
trivy image --severity CRITICAL,HIGH nginx:latest
# Alpine-based — minimal findings
trivy image --severity CRITICAL,HIGH nginx:1.26-alpineUse this to make an informed base image choice early in your Dockerfile.
Scan a Locally Built Image
docker build -t myapp:dev .
trivy image --severity CRITICAL,HIGH myapp:devScan a Private Registry Image
AWS ECR:
aws ecr get-login-password --region us-east-1 \
| docker login --username AWS --password-stdin \
123456789012.dkr.ecr.us-east-1.amazonaws.com
trivy image 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:latestAzure Container Registry:
az acr login --name myregistry
trivy image myregistry.azurecr.io/myapp:latestGoogle Artifact Registry:
gcloud auth configure-docker us-central1-docker.pkg.dev
trivy image us-central1-docker.pkg.dev/myproject/myrepo/myapp:latestStep 3: Output Formats
Trivy supports multiple output formats for integration with other tools.
# Default table output
trivy image nginx:latest
# JSON (for programmatic processing or dashboards)
trivy image --format json --output trivy-report.json nginx:latest
# SARIF (upload to GitHub Security tab / Azure DevOps)
trivy image --format sarif --output trivy-results.sarif nginx:latest
# CycloneDX SBOM (Software Bill of Materials)
trivy image --format cyclonedx --output sbom.json nginx:latest
# SPDX SBOM
trivy image --format spdx-json --output sbom-spdx.json nginx:latest
# HTML report
trivy image \
--format template \
--template "@contrib/html.tpl" \
--output report.html \
nginx:latestThe SARIF format is particularly useful — GitHub natively renders SARIF findings in the Security tab of any repository.
Step 4: Scan Infrastructure as Code
Dockerfile Scanning
Create a deliberately insecure Dockerfile to test with:
# insecure.Dockerfile
FROM ubuntu:18.04
USER root
COPY . /app
RUN apt-get update && apt-get install -y curl wget
EXPOSE 22
CMD ["/bin/bash"]Scan it:
trivy config --file-patterns "Dockerfile:insecure.Dockerfile" .Trivy will flag issues like running as root, exposing port 22, and using a deprecated base image.
Kubernetes Manifest Scanning
# Scan a single manifest
trivy config deployment.yaml
# Scan an entire manifests directory
trivy config ./k8s/
# Scan a Helm chart
trivy config ./charts/myapp/Common findings in Kubernetes manifests:
AVD-KSV-0014— Container running as root (runAsUser: 0)AVD-KSV-0011— CPU limits not setAVD-KSV-0016— Privileged container (privileged: true)AVD-KSV-0030— Default service account token mounted unnecessarily
Example secure pod security context:
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]Terraform Scanning
trivy config ./terraform/Trivy checks Terraform for misconfigurations like:
- S3 buckets without server-side encryption or versioning
- Security groups with
0.0.0.0/0ingress on sensitive ports - RDS instances without encryption at rest
- Lambda functions with overly permissive IAM roles
- CloudTrail logging disabled
Step 5: Secret Detection
Trivy scans for accidentally committed secrets across filesystems and Git repositories.
# Scan a local directory
trivy fs --scanners secret .
# Scan a Git repo (clones and scans)
trivy repo https://github.com/myorg/myrepo
# Combine vuln, config, and secret scanning
trivy fs --scanners vuln,config,secret .Trivy detects 150+ secret patterns including: AWS access keys, GitHub personal access tokens, Slack webhooks, JWT secrets, private RSA/EC keys, database connection strings, and SendGrid/Twilio/Stripe API keys.
Test Secret Detection
# Create a harmless test file (fake key format, safe to use in testing)
echo 'AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' > /tmp/test-secret.txt
trivy fs --scanners secret /tmp/test-secret.txt
rm /tmp/test-secret.txtYou should see a HIGH severity finding for the AWS secret.
Step 6: GitHub Actions Integration
Add Trivy to your CI/CD pipeline to block vulnerable images and misconfigured IaC before they merge.
Create .github/workflows/trivy-scan.yml:
name: Trivy Security Scan
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
- cron: "0 6 * * 1" # Weekly Monday 6am UTC
permissions:
contents: read
security-events: write # Required to upload SARIF results
jobs:
scan-image:
name: Container Image Scan
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Cache Trivy DB
uses: actions/cache@v4
with:
path: ~/.cache/trivy
key: trivy-db-${{ github.run_id }}
restore-keys: trivy-db-
- name: Scan image with Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: sarif
output: trivy-image.sarif
severity: CRITICAL,HIGH
exit-code: "1" # Fail the build on findings
ignore-unfixed: true # Skip CVEs without a fix available
- name: Upload results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always() # Upload even when the scan step fails
with:
sarif_file: trivy-image.sarif
category: container-image
scan-iac:
name: IaC and Secret Scan
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache Trivy DB
uses: actions/cache@v4
with:
path: ~/.cache/trivy
key: trivy-db-${{ github.run_id }}
restore-keys: trivy-db-
- name: Scan IaC configs
uses: aquasecurity/trivy-action@master
with:
scan-type: config
scan-ref: "."
format: sarif
output: trivy-iac.sarif
exit-code: "1"
- name: Scan for secrets
uses: aquasecurity/trivy-action@master
with:
scan-type: fs
scan-ref: "."
scanners: secret
format: sarif
output: trivy-secrets.sarif
exit-code: "1"
- name: Upload IaC results
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-iac.sarif
category: iac
- name: Upload secret scan results
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-secrets.sarif
category: secretsAfter the first run, findings appear under Security → Code scanning alerts in your GitHub repository.
Step 7: Advanced Configuration
.trivyignore — Suppress False Positives
Create a .trivyignore file at the repository root to suppress accepted risks:
# .trivyignore
# Format: <ID> [exp:<YYYY-MM-DD>] [ack:<note>]
# No fix available; mitigated by network segmentation policy
CVE-2023-44487
# Accepted false positive — only triggered in a path we don't execute
CVE-2024-21626 exp:2026-09-01 ack:reviewed by security team 2026-03-15
# IaC check — intentional, Terraform state bucket (no versioning needed)
AVD-AWS-0090
# Suppress a whole check type in a specific file
AVD-KSV-0020Reference the file when scanning:
trivy image --ignorefile .trivyignore myapp:latesttrivy.yaml — Reusable Configuration
Avoid repeating flags by creating a trivy.yaml config file:
# trivy.yaml
scan:
security-checks:
- vuln
- config
- secret
severity:
- CRITICAL
- HIGH
format: table
exit-code: 0 # 0 for local dev; override to 1 in CI
image:
ignore-unfixed: true
db:
skip-update: false
cache:
dir: ~/.cache/trivyUse it with any scan command:
trivy image --config trivy.yaml myapp:latest
trivy config --config trivy.yaml ./terraform/Air-Gapped / Offline Environments
In environments without internet access, pre-download the database and transfer it:
# On an internet-connected machine — download the DB bundle
trivy image --download-db-only
tar czf trivy-db.tar.gz ~/.cache/trivy/db/
# Transfer trivy-db.tar.gz to the air-gapped host, then:
mkdir -p ~/.cache/trivy/db/
tar xzf trivy-db.tar.gz -C ~/.cache/
# Scan without updating the DB
trivy image --skip-db-update --offline-scan myapp:latestVerification
Run these checks to confirm your Trivy setup is working correctly.
1. Confirm Vulnerability Detection
Use a known-vulnerable image (safe to pull, just don't run it):
trivy image --severity CRITICAL python:3.6-slim 2>/dev/null | grep "CRITICAL"You should see multiple CRITICAL findings — Python 3.6 is end-of-life with many unpatched CVEs.
2. Confirm IaC Scanning Works
cat > /tmp/test-pod.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
name: insecure-pod
spec:
containers:
- name: app
image: nginx:latest
securityContext:
privileged: true
runAsUser: 0
EOF
trivy config /tmp/test-pod.yaml
rm /tmp/test-pod.yamlExpected: CRITICAL/HIGH findings for privileged: true and root user.
3. Confirm Secret Scanning Works
cat > /tmp/test-creds.env << 'EOF'
# Fake credentials — safe test data only
GITHUB_TOKEN=ghp_aBcDeFgHiJkLmNoPqRsTuVwXyZ123456
DATABASE_URL=postgresql://admin:s3cr3tpassword@db.internal/prod
EOF
trivy fs --scanners secret /tmp/test-creds.env
rm /tmp/test-creds.envExpected: HIGH/CRITICAL findings for GitHub token and database credentials.
4. Verify GitHub Actions Results
After pushing your workflow:
gh run list --workflow=trivy-scan.yml --limit 5
gh run view <run-id> --logNavigate to Security → Code scanning alerts in GitHub to confirm SARIF results uploaded successfully.
Troubleshooting
Database Download Fails
# Test connectivity to the Trivy DB registry
curl -I https://ghcr.io
# Force a fresh database download
trivy image --clear-cache nginx:latest
# Use the AWS mirror (useful behind proxies)
trivy image \
--db-repository public.ecr.aws/aquasecurity/trivy-db \
nginx:latest"No Such Image" Error
# Pull the image first, then scan
docker pull myapp:latest
trivy image myapp:latest
# Or tell Trivy to pull from the registry directly
trivy image --image-src registry myapp:latestScan Is Slow or Times Out
# Reduce parallelism to avoid memory pressure
trivy image --parallel 1 myapp:latest
# Skip specific large directories (e.g., bundled JDK)
trivy image --skip-dirs /usr/local/java myapp:latest
# Only scan OS packages (skip language dependencies)
trivy image --vuln-type os myapp:latestToo Many False Positives in CI
# Skip CVEs that have no available fix
trivy image --ignore-unfixed myapp:latest
# Add a project-wide .trivyignore for accepted risks
echo "CVE-2024-XXXXX exp:2026-12-31 ack:no-fix-upstream" >> .trivyignore
# Set a minimum score threshold
trivy image --severity CRITICAL myapp:latestGitHub Actions SARIF Upload Rejected
The workflow job needs security-events: write permission. Confirm the permissions block is present:
permissions:
contents: read
security-events: writeIf your organization restricts third-party Actions, pin the action to a specific SHA:
uses: aquasecurity/trivy-action@a20de5420d57c4102486cdd9349b532bf5b12c7 # v0.20.0Summary
You've built a full container security scanning pipeline with Trivy:
| Task | Command |
|---|---|
| Scan a container image | trivy image --severity CRITICAL,HIGH myapp:latest |
| Scan Dockerfile / K8s / Terraform | trivy config ./infra/ |
| Detect committed secrets | trivy fs --scanners secret . |
| Generate SARIF for GitHub | --format sarif --output results.sarif |
| Scan a Git repository | trivy repo https://github.com/org/repo |
| Suppress known false positives | .trivyignore file |
| Offline / air-gapped scanning | --skip-db-update --offline-scan |
Trivy's breadth — one binary covering OS packages, app dependencies, IaC misconfigurations, and secrets — makes it the go-to choice for DevSecOps teams. Start with image scanning and severity filtering in CI, then progressively add IaC and secret detection as your pipeline matures.
Where to go next:
- Run
trivy k8s --report summary clusterto scan a live Kubernetes cluster - Deploy the Trivy Operator for continuous in-cluster scanning with automatic policy enforcement
- Integrate Trivy with Harbor or AWS ECR to block vulnerable images at the registry level before they ever reach a deployment pipeline