Week 2 — Day 11: Container Image Scanning with Trivy
A full walkthrough of Trivy — scanning container images, filesystems, Git repos, and IaC files for vulnerabilities and misconfigurations, plus integrating it into GitHub Actions.
What is Trivy?
Trivy is an open-source, all-in-one security scanner by Aqua Security. It scans for:
| Target | What it finds |
|---|---|
| Container images | OS package CVEs, language library CVEs |
| Filesystems | Same as above, on local directories |
| Git repositories | Secrets, CVEs in dependency files |
| IaC files | Misconfigurations in Terraform, Dockerfile, K8s manifests |
| SBOM | Vulnerabilities in a Software Bill of Materials |
Trivy is fast, has no server component, and is the most widely used container scanner in CI/CD pipelines.
Installation
1
2
3
4
5
6
7
8
# Windows (via Scoop)
scoop install trivy
# Or download the binary from GitHub releases
# https://github.com/aquasecurity/trivy/releases
# Verify
trivy --version
Scanning Container Images
Basic Scan
1
trivy image nginx:latest
[SCREENSHOT]— Terminal showing trivy image nginx:latest output — a table with columns: Library, Vulnerability ID, Severity, Installed Version, Fixed Version, Title
Trivy scans:
- The OS packages (apt/apk/yum)
- Application libraries (pip, npm, gem, cargo, etc.)
- For each: checks its vulnerability database (NVD, GitHub Advisory, OS vendor advisories)
Filtering by Severity
1
2
3
4
5
# Only show Critical and High
trivy image --severity CRITICAL,HIGH nginx:latest
# Only Critical
trivy image --severity CRITICAL nginx:latest
[SCREENSHOT]— Terminal showing filtered output with only CRITICAL and HIGH rows, making the output much more manageable
Failing CI on Critical Findings
1
2
# Exit code 1 if any CRITICAL CVE is found — use this in CI to block the pipeline
trivy image --severity CRITICAL --exit-code 1 myapp:latest
If the scan finds a Critical CVE, the command exits with code 1, failing the pipeline step.
Scanning a Specific Architecture
1
trivy image --platform linux/amd64 myapp:latest
Important when building multi-arch images on an ARM Mac — scan the architecture that will actually run in production.
Output Formats
1
2
3
4
5
6
7
8
# JSON output (for parsing or feeding into other tools)
trivy image --format json --output results.json nginx:latest
# SARIF output (for GitHub Code Scanning)
trivy image --format sarif --output results.sarif nginx:latest
# Table (default)
trivy image --format table nginx:latest
[SCREENSHOT]— Terminal showing trivy image –format json output piped through jq showing the structured vulnerability data
Scanning Filesystems and Repos
Scan a Local Directory
1
2
3
4
# Scan your project directory for vulnerabilities in dependency files
trivy fs .
# This picks up: package-lock.json, requirements.txt, Gemfile.lock, go.sum, etc.
[SCREENSHOT]— Terminal showing trivy fs . output scanning a Node.js project directory and finding CVEs in node_modules packages
Scan a Git Repository
1
2
3
4
5
# Scan a remote repo without cloning
trivy repo https://github.com/yourusername/yourrepo
# Scan and also check for secrets
trivy repo --scanners vuln,secret https://github.com/yourusername/yourrepo
[SCREENSHOT]— Terminal showing trivy repo scanning a GitHub repo URL and listing found vulnerabilities from the dependency files
Scanning IaC Files
Trivy checks Dockerfiles, Terraform, Kubernetes manifests, and Helm charts for misconfigurations.
1
2
3
4
5
6
7
8
# Scan a Dockerfile
trivy config Dockerfile
# Scan a Terraform directory
trivy config ./terraform/
# Scan a Kubernetes manifest
trivy config k8s-deployment.yaml
[SCREENSHOT]— Terminal showing trivy config Dockerfile output listing misconfigurations like “Specify at least 1 USER command” and “Do not use sudo” with their severity levels
1
2
# Scan your MindCraft Terraform code
trivy config ./terraform/ --severity MEDIUM,HIGH,CRITICAL
[SCREENSHOT]— Terminal showing trivy config scanning the Terraform directory and finding misconfigurations like open security group rules or unencrypted S3 buckets
Secret Scanning
Trivy can scan for hardcoded secrets (API keys, passwords, tokens) in your codebase:
1
trivy fs --scanners secret .
[SCREENSHOT]— Terminal showing trivy fs –scanners secret finding a hardcoded AWS access key in a config file, with the file path and line number shown
Trivy detects secrets like:
- AWS access keys
- GitHub tokens
- Private keys (RSA, EC)
- Generic API key patterns
Understanding CVE Output
Each finding shows:
| Field | Meaning |
|---|---|
| Library | The package with the vulnerability |
| Vulnerability ID | CVE identifier |
| Severity | CRITICAL / HIGH / MEDIUM / LOW |
| Installed Version | What you have |
| Fixed Version | What you need to upgrade to |
| Title | Short description of the vulnerability |
When “Fixed Version” is empty: No fix is available yet. Your options are:
- Accept the risk (document it)
- Switch to a different package
- Use a different base image that doesn’t include this package
Ignoring False Positives
Create a .trivyignore file in your project to suppress known false positives or accepted risks:
1
2
3
4
5
6
7
# .trivyignore
# Accepted risk — no fix available, low exploitability in our context
CVE-2023-12345
# This CVE is in a test-only dependency, not in production
CVE-2023-67890
1
trivy image --ignorefile .trivyignore myapp:latest
[SCREENSHOT]— Terminal showing trivy image with –ignorefile applied, and the previously shown CVE now absent from the output with a note “1 vulnerability suppressed by .trivyignore”
Integrating Trivy into GitHub Actions
Basic Image Scan
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# .github/workflows/trivy.yml
name: Trivy Security Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
trivy-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t myapp:$ .
- name: Run Trivy image scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:$
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH
exit-code: 1
- name: Upload results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-results.sarif
[SCREENSHOT]— GitHub Actions run showing the Trivy scan step completing — either green (no critical CVEs) or red with the CVEs listed in the step output
The SARIF upload makes findings appear in the GitHub Security tab → Code scanning alerts.
[SCREENSHOT]— GitHub repository → Security tab → Code scanning showing Trivy findings listed with severity badges and the affected file/package
Full Pipeline with IaC + Image Scan
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
name: Full Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Trivy IaC scan
uses: aquasecurity/trivy-action@master
with:
scan-type: config
scan-ref: .
severity: HIGH,CRITICAL
exit-code: 0 # don't fail on IaC findings (informational for now)
- name: Build image
run: docker build -t myapp:test .
- name: Trivy image scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:test
severity: CRITICAL
exit-code: 1 # fail on Critical image CVEs
Lab — Scan a Public Image and Fix It
Objective: Scan an old image, understand the findings, update the base image, re-scan.
- Scan an old Python image:
1
trivy image --severity CRITICAL,HIGH python:3.9
[SCREENSHOT]— Terminal showing trivy image python:3.9 output with multiple CRITICAL and HIGH CVEs listed
- Count the Critical findings:
1
trivy image --severity CRITICAL --format json python:3.9 | jq '.Results[].Vulnerabilities | length'
- Scan the latest slim version:
1
trivy image --severity CRITICAL,HIGH python:3.12-slim
[SCREENSHOT]— Terminal showing trivy image python:3.12-slim with significantly fewer or zero CRITICAL findings — demonstrating the impact of keeping the base image updated
- Update your Dockerfile
FROMline and rebuild - Re-scan your built image — verify CVE count dropped
Key Takeaways
- Trivy is a single binary that scans images, filesystems, repos, IaC, and secrets — use it everywhere
- Add
--exit-code 1 --severity CRITICALto your CI pipeline to block on critical CVEs - Upload SARIF results to GitHub Security tab for persistent tracking
- Keep base images updated — most CVEs are in outdated OS packages
- Use
.trivyignorefor accepted risks, not as a way to silence everything - Scan both the image AND the IaC in the same pipeline

