Docker Security Scanning with Trivy
Use Trivy to find vulnerabilities in Docker images, Dockerfiles, and IaC before they reach production. Includes CI pipeline integration examples.
What you'll learn
- ✓What Trivy scans and how its vulnerability database works
- ✓Scanning Docker images for OS and library CVEs
- ✓Scanning Dockerfiles and IaC for misconfigurations
- ✓Integrating Trivy into GitHub Actions and GitLab CI
- ✓Filtering results by severity and setting exit codes for CI gates
Prerequisites
None — this post is self-contained.
A clean docker build tells you nothing about whether your image ships with known vulnerabilities. Base images carry OS packages you never asked for, and your application dependencies pull in transitive libraries with their own CVE histories. Trivy is an open-source scanner from Aqua Security that checks all of this in a single command.
Installing Trivy
On macOS with Homebrew:
brew install trivy
On Linux:
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
Or run it as a Docker container, which is convenient in CI:
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:latest image myapp:latest
Scanning a Docker Image
The most common use case is scanning a built image for known vulnerabilities:
trivy image myapp:latest
Trivy downloads its vulnerability database on first run and caches it locally. The output groups findings by target (OS packages, language-specific libraries) and sorts by severity:
myapp:latest (ubuntu 24.04)
Total: 12 (UNKNOWN: 0, LOW: 5, MEDIUM: 4, HIGH: 2, CRITICAL: 1)
+-----------+------------------+----------+-------------------+
| Library | Vulnerability | Severity | Installed Version |
+-----------+------------------+----------+-------------------+
| libssl3 | CVE-2024-XXXXX | CRITICAL | 3.0.13-0ubuntu3 |
| libcurl4 | CVE-2024-YYYYY | HIGH | 8.5.0-2ubuntu10 |
+-----------+------------------+----------+-------------------+
Filtering by Severity
In CI, you typically want to fail the build only on serious issues. Use the --severity flag to filter and --exit-code to control the return code:
# Fail the build only on CRITICAL and HIGH vulnerabilities
trivy image --severity CRITICAL,HIGH --exit-code 1 myapp:latest
Exit code 1 causes the CI step to fail when matching vulnerabilities are found. You can combine this with --ignore-unfixed to skip vulnerabilities that have no available patch:
trivy image \
--severity CRITICAL,HIGH \
--ignore-unfixed \
--exit-code 1 \
myapp:latest
This is a practical default for most teams: alert on serious issues that you can actually fix.
Scanning Dockerfiles for Misconfigurations
Trivy goes beyond CVE scanning. It can analyze your Dockerfile for security misconfigurations before you even build the image:
trivy config ./Dockerfile
This catches issues like running as root, using ADD instead of COPY, exposing unnecessary ports, and missing HEALTHCHECK instructions. Example output:
Dockerfile (dockerfile)
Tests: 23 (SUCCESSES: 20, FAILURES: 3)
Failures: 3 (LOW: 1, MEDIUM: 1, HIGH: 1)
HIGH: Specify at least one USER command in Dockerfile
────────────────────────────────────────────────────
Running containers as root is a security risk.
Add a USER instruction to run the container as non-root.
Scanning the Filesystem
If you want to scan application dependencies without building an image first, point Trivy at your project directory:
trivy fs --scanners vuln .
Trivy detects lock files for most ecosystems: package-lock.json, yarn.lock, Gemfile.lock, go.sum, requirements.txt, pom.xml, and more. This is useful in early development when you want fast feedback before committing to a full image build.
Output Formats
Trivy supports multiple output formats for integration with other tools:
# JSON for programmatic consumption
trivy image --format json --output results.json myapp:latest
# SARIF for GitHub Security tab integration
trivy image --format sarif --output results.sarif myapp:latest
# Table (default, human-readable)
trivy image --format table myapp:latest
# Template-based custom output
trivy image --format template \
--template "@contrib/html.tpl" \
--output report.html myapp:latest
The SARIF format is particularly valuable because GitHub, GitLab, and Azure DevOps can ingest it and display findings directly in pull request annotations.
GitHub Actions Integration
A minimal workflow that scans every push:
name: Container Security Scan
on:
push:
branches: [main]
pull_request:
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: CRITICAL,HIGH
exit-code: 1
ignore-unfixed: true
format: sarif
output: trivy-results.sarif
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-results.sarif
The if: always() on the upload step ensures results appear in the Security tab even when the scan finds vulnerabilities and the previous step fails.
GitLab CI Integration
container_scanning:
stage: test
image:
name: aquasec/trivy:latest
entrypoint: [""]
script:
- trivy image
--severity CRITICAL,HIGH
--ignore-unfixed
--exit-code 1
--format json
--output gl-container-scanning-report.json
$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
artifacts:
reports:
container_scanning: gl-container-scanning-report.json
Ignoring Known Vulnerabilities
Some CVEs are false positives or accepted risks. Create a .trivyignore file in your repository root:
# Accepted risk: not exploitable in our context
CVE-2024-12345
# Vendor dispute, no patch available
CVE-2024-67890
Trivy reads this file automatically. For more structured policies, use Trivy’s OPA-based policy engine:
trivy image --policy ./policies/ myapp:latest
Scanning in a Private Registry
For images in private registries, authenticate before scanning:
# Docker Hub or any registry
docker login registry.example.com
trivy image registry.example.com/myapp:latest
# AWS ECR
aws ecr get-login-password | docker login --username AWS --password-stdin \
123456789.dkr.ecr.us-east-1.amazonaws.com
trivy image 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:latest
Trivy respects Docker’s credential store, so any registry you can docker pull from, Trivy can scan.
A Practical Scanning Strategy
Start by scanning your existing images to understand your baseline. Run trivy image --severity CRITICAL against every production image and fix what you can. Then integrate Trivy into CI with --exit-code 1 on CRITICAL severity only. As your team matures, expand the gate to include HIGH severity and add Dockerfile misconfiguration scanning. The goal is to ratchet quality upward without blocking every pull request on day one.
Related articles
- Docker Docker Scout: Find and Fix Image Vulnerabilities
Learn how to use Docker Scout to scan container images for CVEs, analyze software dependencies, and get actionable remediation advice directly from the CLI.
- Docker Docker tmpfs Mounts and In-Memory Storage
Use Docker tmpfs mounts to store sensitive or ephemeral data in memory, keeping it off disk and out of container layers entirely.
- Docker Docker Security Best Practices for Production
Harden Docker images and runtimes: non-root users, minimal bases, secret handling, capability drops, and image signing - without making developers miserable.
- Docker Docker vs Podman: Which Container Runtime to Use
Compare Docker and Podman on architecture, security, compatibility, and developer experience. Learn which container runtime fits your workflow in 2026.