Lab 3: Security Scanning with Trivy
Time: 20 minutes
Goal: Scan container images for vulnerabilities and learn to fix common security issues
The Problem
Your images work, but are they secure? Container images can contain:
- Vulnerable OS packages (CVEs in base image)
- Outdated application dependencies with known exploits
- Misconfigurations that expose your application to attacks
Trivy is a vulnerability scanner that helps you find and fix these issues before deployment.
Part 1: Install Trivy
In Codespaces / Local Linux
# Download and install Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.50.0
# Verify installation
trivy --versionAlternative: Use Trivy via Docker
If you can't install locally, run Trivy as a container:
alias trivy="docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy:latest"
trivy --versionPart 2: Scan a Public Image
Let's start by scanning a well-known image to understand the output.
Scan Alpine
trivy image alpine:3.18You'll see output like:
alpine:3.18 (alpine 3.18.4)
===========================
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
Alpine is usually clean! Let's try something older...
Scan an Older Python Image
trivy image python:3.9-slimThis will likely show vulnerabilities:
python:3.9-slim (debian 11.8)
==============================
Total: 45 (UNKNOWN: 0, LOW: 12, MEDIUM: 18, HIGH: 12, CRITICAL: 3)
┌──────────────────┬────────────────┬──────────┬────────────┬───────────────────┬───────────────┬────────────────────────────────────┐
│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │
├──────────────────┼────────────────┼──────────┼────────────┼───────────────────┼───────────────┼────────────────────────────────────┤
│ libssl1.1 │ CVE-2023-12345 │ CRITICAL │ fixed │ 1.1.1n-0 │ 1.1.1w-0 │ OpenSSL: Remote Code Execution │
│ libc6 │ CVE-2023-54321 │ HIGH │ fixed │ 2.31-13 │ 2.31-15 │ glibc: Buffer Overflow │
└──────────────────┴────────────────┴──────────┴────────────┴───────────────────┴───────────────┴────────────────────────────────────┘
Part 3: Understand CVE Severity Levels
Trivy categorizes vulnerabilities by severity:
| Severity | Meaning | Action |
|---|---|---|
| CRITICAL | Actively exploited, remote code execution | Fix immediately |
| HIGH | Serious impact, requires user interaction | Fix before production |
| MEDIUM | Moderate impact, limited scope | Fix when convenient |
| LOW | Minor issues, theoretical exploits | Optional to fix |
| UNKNOWN | Not yet categorized | Investigate |
Production Rule: No CRITICAL or HIGH vulnerabilities.
Part 4: Build and Scan a Vulnerable Image
Setup
cd week-02/labs/lab-03-security-scanning/starter
lsYou'll see:
app.py- Simple Flask apprequirements.txt- Deliberately outdated dependenciesDockerfile- Uses an old base image
Build the Vulnerable Image
docker build -t vulnerable-app:v1 .Scan It
trivy image vulnerable-app:v1Challenge: Count the vulnerabilities:
- How many CRITICAL?
- How many HIGH?
- What packages are affected?
Filter by Severity
See only CRITICAL and HIGH:
trivy image --severity CRITICAL,HIGH vulnerable-app:v1Output to a File
trivy image --severity CRITICAL,HIGH -f table -o scan-results.txt vulnerable-app:v1
cat scan-results.txtPart 5: Fix the Vulnerabilities
Strategy 1: Upgrade the Base Image
Open Dockerfile:
FROM python:3.9-slim # Old version with vulnerabilitiesChange to the latest:
FROM python:3.11-slim # Newer, more secureRebuild and rescan:
docker build -t vulnerable-app:v2 .
trivy image --severity CRITICAL,HIGH vulnerable-app:v2Result: Fewer OS-level vulnerabilities!
Strategy 2: Upgrade Application Dependencies
Open requirements.txt:
flask==2.0.1 # Old version
requests==2.25.1 # Old version
Update to latest stable versions:
flask==3.0.0
requests==2.31.0
Rebuild and rescan:
docker build -t vulnerable-app:v3 .
trivy image --severity CRITICAL,HIGH vulnerable-app:v3Result: Application dependency vulnerabilities fixed!
Strategy 3: Use Multi-Stage Builds (Bonus)
Even better - use multi-stage to reduce the attack surface:
# Build stage
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt
# Runtime stage
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY app.py .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]This removes pip and setuptools from the final image.
Part 6: Scan for Misconfigurations
Trivy can also find Docker misconfigurations:
trivy config DockerfileThis checks for:
- Running as root (security risk)
- Missing health checks
- Using
latesttag - Exposed secrets
Example output:
Dockerfile (dockerfile)
=======================
Tests: 23 (SUCCESSES: 20, FAILURES: 3)
Failures: 3
CRITICAL: Specify at least 1 USER command
──────────────────────────────────────────
Containers should not run as root
4 [ WORKDIR /app
5 [ COPY . .
6 [ CMD ["python", "app.py"]
Part 7: Continuous Scanning Workflow
Scan Before You Push
Add this to your workflow:
# Build
docker build -t myapp:latest .
# Scan
trivy image --severity CRITICAL,HIGH --exit-code 1 myapp:latest
# If scan passes (exit code 0), push
docker push myapp:latestThe --exit-code 1 flag makes Trivy return a non-zero exit code if vulnerabilities are found, which fails CI/CD pipelines.
GitHub Actions Integration
# .github/workflows/security-scan.yml
name: Security Scan
on: [push]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t ${{ github.repository }}:${{ github.sha }} .
- name: Run Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ github.repository }}:${{ github.sha }}
severity: 'CRITICAL,HIGH'
exit-code: '1'Part 8: Understanding False Positives
Sometimes Trivy reports vulnerabilities that don't apply to your use case.
Example: Debian Package Vulnerabilities
libcurl4: CVE-2023-XXXXX (CRITICAL)
Questions to ask:
- Does my application actually use this package?
- Is the vulnerable code path reachable in my container?
- Is there a compensating control (firewall, network policy)?
Ignoring Specific CVEs
Create .trivyignore:
# This CVE doesn't affect our use case
CVE-2023-12345
# This package isn't used by our application
CVE-2023-54321
Use with caution and document WHY you're ignoring it!
Part 9 (Optional): Generate Trivy Trend Charts from Real Scans
If you want measured before/after vulnerability trends for v1, v2, and v3:
cd week-02/labs/lab-03-security-scanning
python3 scripts/benchmark_trivy_scan.pyRequirements:
- Docker running
- Trivy installed (
trivy --version) - Python 3
matplotlibinstalled (for PNG chart output)
If matplotlib is not available yet, you can still collect scan data:
python3 scripts/benchmark_trivy_scan.py --no-chartsArtifacts are written to:
assets/generated/week-02-trivy-scan/
trivy_severity_stacked.png
trivy_high_critical_trend.png
summary.md
results.json
logs/
scans/
Checkpoint ✅
You should now be able to:
- Install and run Trivy
- Scan images and interpret CVE reports
- Understand severity levels (CRITICAL, HIGH, MEDIUM, LOW)
- Fix vulnerabilities by upgrading base images
- Fix vulnerabilities by upgrading dependencies
- Scan Dockerfiles for misconfigurations
- Integrate Trivy into a CI/CD pipeline
Challenge: Achieve Zero CRITICAL Vulnerabilities
Using the starter image:
- Build and scan to see the baseline
- Upgrade base image to latest Python slim
- Update all dependencies to latest stable versions
- Add a non-root USER to the Dockerfile
- Scan again - are all CRITICAL and HIGH vulnerabilities gone?
Goal: Total: X (CRITICAL: 0, HIGH: 0)
Real-World Security Practices
What to Fix First
Priority order:
- CRITICAL in network-exposed services - Immediate fix
- HIGH in production - Fix before next deploy
- MEDIUM - Fix in next sprint
- LOW - Fix when convenient
Security is a Process, Not a Point-in-Time
- Scan regularly (daily in CI/CD)
- New CVEs are published constantly
- An image that's clean today might be vulnerable tomorrow
- Automate scanning in your deployment pipeline
Defense in Depth
Image scanning is ONE layer:
- Network policies - Limit what containers can reach
- Runtime security - Detect anomalous behavior (Falco)
- Least privilege - Run as non-root, drop capabilities
- Immutable infrastructure - Don't patch, rebuild
Clean Up
docker rmi vulnerable-app:v1 vulnerable-app:v2 vulnerable-app:v3
rm scan-results.txtResources
Demo
Key Takeaway
Scan early, scan often. Fix CRITICAL and HIGH before production.
Security isn't a checkbox—it's a continuous process.



