Every image contains an operating system layer and libraries, and some of those packages have known vulnerabilities (CVEs). A scanner compares what is inside an image with public vulnerability databases, so you find out before an attacker does. Trivy, an open-source scanner from Aqua Security, is simple enough to run on a laptop and in CI with no server to maintain.
Versions in use on this date. Trivy 0.14 and Docker Engine 20.10.2 (released 2 January 2021). Trivy reads OS packages (Alpine, Debian, Ubuntu, Red Hat, Amazon Linux and others) and application dependencies (npm, pip, Maven, Go modules and more) from lock files inside the image.
Where scanning fits
flowchart TB
DEV[Developer commit] --> CI[CI build]
CI --> IMG[Build image with Docker]
IMG --> SC{Trivy scan<br/>HIGH and CRITICAL with fixes}
SC -->|found| FAIL[Fail the build<br/>report CVE list]
SC -->|clean| PUSH[Push to registry]
PUSH --> DEPLOY[Deploy]
REG[Registry] --> NIGHT[Scheduled nightly rescan<br/>of images in production]
NIGHT -->|new CVE published| TICKET[Open ticket to rebuild]
DB[(Vulnerability database<br/>updated several times a day)] -.-> SC
DB -.-> NIGHT
A clean scan today does not stay clean: new CVEs are published daily against software you already shipped, which is why scheduled rescans matter as much as build-time scans.
Install and first scan
# Debian/Ubuntu package from the Aqua repository, or use the container
sudo apt install -y wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt update && sudo apt install -y trivy
trivy image alpine:3.12
trivy image --severity HIGH,CRITICAL node:12-alpine
Or without installing anything:
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v $HOME/.cache:/root/.cache \
aquasec/trivy:0.14.0 image --severity HIGH,CRITICAL myapp:1.0.0
The first run downloads the vulnerability database (tens of MB); mounting a cache directory avoids repeating it. The output lists package, installed version, fixed version, severity and a CVE link.
Useful options
| Option | Purpose |
|---|---|
--severity HIGH,CRITICAL |
Report only what you will act on |
--ignore-unfixed |
Skip CVEs with no patch available yet |
--exit-code 1 |
Return failure when findings remain, so CI stops |
--format json -o report.json |
Machine-readable output |
--vuln-type os,library |
Choose OS packages, application libraries, or both |
--skip-update |
Use the cached database (offline or rate-limited runs) |
trivy fs . |
Scan a project directory and its lock files before building |
trivy image --exit-code 1 --severity CRITICAL --ignore-unfixed myapp:1.0.0
echo $? # 1 means the gate failed
Handling findings sensibly
-
Fix by rebuilding: most OS CVEs disappear when you rebuild on a newer base image (
FROM node:12-alpinepulls current patches only if you actually pull again). Rebuild withdocker build --pull. -
Update the library shown in the “fixed version” column, then regenerate the lock file.
-
Switch to a smaller base (Alpine, slim or distroless images) to remove packages you never use.
-
Accept risk deliberately. For a CVE that is not exploitable in your context, list it in
.trivyignore, one ID per line, with a comment explaining why and a review date:# CVE-2020-XXXXX not reachable: we do not use the affected module. Review 2021-04-01 CVE-2020-XXXXX -
Do not treat the severity as the full story: check whether the vulnerable code runs in your service, whether it is network-reachable, and whether a fix exists.
GitHub Actions example
name: build
on: [push]
jobs:
image:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- run: docker build -t myapp:${{ github.sha }} .
- uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: CRITICAL,HIGH
ignore-unfixed: true
exit-code: '1'
Pin the action to a released version tag rather than master once you settle on one. A scheduled workflow (on: schedule) scanning the images that are currently deployed gives you the nightly rescan from the diagram.
Limits to remember
Scanners find known CVEs in recognised packages. They do not find bugs in your own code, secrets baked into layers, bad configuration or software installed from tarballs without a package record. Combine scanning with minimal images, non-root users, a read-only filesystem and secret scanning (for example in Git hooks). Record scan results (pipeline artifacts) so you can answer “were we affected?” when a new CVE hits the news.