sehaj/learn
Docker/07 Production

07.03 · with code · 12 min

Scanning images

Scan images for known vulnerabilities with Trivy, read the numbers without panicking, and gate CI on the findings you can actually fix.

The core idea

An image contains hundreds of packages you didn't write: the OS, the language runtime, your dependencies. Some of them have known vulnerabilities (CVEs). A scanner lists every package in the image and matches it against vulnerability databases. The skill isn't running the scanner; it's reading the result: which findings matter, and which you can fix.

Run a scan

Trivy is a free, open-source scanner that runs as a container, so there's nothing to install:

scan
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
-v trivy-cache:/root/.cache aquasec/trivy:0.76.1 image notes-api

The socket mount lets Trivy read your local images; the cache volume keeps its vulnerability database between runs. (Docker Desktop also includes Docker Scout, docker scout cves, which does the same job with a Docker account.)

The numbers

Counts from this course's scans, all with the same Trivy version on the same day:

ImageCriticalHighMediumLowTotal
node:2220455204217894397
notes API on node:22-slim14211084239
node:22-alpine01012123

The full Debian image has nearly twenty times the findings of the slim one, because it ships far more packages: compilers, git, Python and their libraries. Base image choice (02.07 Choosing a base image) is your biggest lever. But 239 still sounds alarming, so filter to what you can act on.

Only what's fixable

Many findings have no fix released yet; you can't do anything about those except choose a different base. Trivy's --ignore-unfixed hides them:

❯terminal
$ trivy image --scanners vuln --ignore-unfixed notes-api
...
notes-api fixable: CRITICAL=0 HIGH=10 MEDIUM=12 LOW=1 total 24

(Summary of Trivy's report.) 239 became 24. Then ask where those 24 live:

❯terminal
$ trivy image --scanners vuln --ignore-unfixed --severity HIGH --format json notes-api | ...
Node.js {'brace-expansion': 5, 'ip-address': 1, 'pacote': 2, 'picomatch': 1, 'sigstore': 1}

Every fixable HIGH was inside npm's own bundled packages, part of the Node.js installation. The notes API never runs npm in production. So the final stage now deletes it in the same step that uses it:

api/Dockerfile
# Install production dependencies, then delete npm itself: the app never runs it, and its bundled
# packages were the only fixable HIGH findings in a scan (see 07.03). Same RUN, so they never reach a layer.
RUN npm ci --omit=dev \
&& rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx
❯terminal
$ trivy image --scanners vuln --ignore-unfixed notes-api
...
notes-api fixable: CRITICAL=0 HIGH=0 MEDIUM=0 LOW=0

Zero fixable findings. The image isn't smaller, because npm also lives in a lower layer from the base image (02.05 Making images small), but scanners and attackers both see the final filesystem, where it's gone.

Smaller isn't automatically safer

You might expect distroless (02.07 Choosing a base image) to win. Built the same day, it didn't:

❯terminal
notes-api:distroless --ignore-unfixed: CRITICAL=1 HIGH=6 MEDIUM=12 LOW=14
notes-api:distroless (debian 12.13) libssl3 CRITICAL 3.0.18-1~deb12u2 -> fixed in 3.0.19-1~deb12u2

Its OpenSSL was a patch release behind the slim image's. Fewer packages is good, but only if they're up to date. Measure; don't assume. And rebuild regularly with docker build --pull so you pick up base-image patches.

Gating CI

A scan nobody reads does nothing, so make the build fail on what matters. --exit-code 1 makes Trivy fail when it finds anything at the chosen severities:

❯terminal
$ trivy image --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 notes-api
$ echo $?
0

Before the npm change, the same command exited with 1. "Fixable HIGH or CRITICAL" is a gate teams can live with: it fails on things you can fix today, not on noise. The workflow in 07.06 Project: ship with GitHub Actions runs exactly this.

Predict first

A scan shows 200 vulnerabilities in your image, 180 with no fix available. What's the most useful next step?

Commit to an answer before you read on.
CheckpointScan, reduce, gate

Scan node:22, node:22-slim and your notes API. Re-run with --ignore-unfixed --severity HIGH,CRITICAL and find which packages the findings come from. Run the gate command and check its exit code.

STUCK? ▸

Different numbers from the table are expected: the vulnerability database changes daily and base images are re-published. Compare images against each other on the same day, not against these numbers.

Recap

Interview questions

SeniorfreqMust-knowDesign

How would you introduce image vulnerability scanning into a team's pipeline?

Report a mistakeEdit this page