13.2 Scan a container image
Lesson 6.4 ended with a promise. You had just learned to look at a container image's page before pulling it, and I said you would meet the other half of that in Module 13, "where a scanner reads the packages inside an image and tells you what's in it that you didn't know you'd installed."
This is that lesson, and it is the cheapest scan in the module: one command, no installation, and about two minutes of waiting.
We start here for a reason. The findings arrive fast, they are the accurate kind from lesson 13.1, and the pile they produce is exactly the problem the next lesson solves. Build the problem before the solution.
The tool
Trivy is a scanner from Aqua Security that reads what is inside a container image and compares it against vulnerability databases. It is one of the two or three tools you will actually meet in this job.
You are not going to install it. Trivy publishes itself as a container image,
so you can run the scanner the same way you run everything else on UBNT01,
using the docker run you already know from lesson 6.4. That also means
there is no version to pin and nothing to upgrade later.
SSH into UBNT01 and start a tmux session, per the habit from lesson 6.2:
ssh sam@10.10.10.20
tmux new -s scan
Give it something worth scanning
Scanning a healthy image teaches nothing, because a clean report looks the same as a broken tool. So pull an image that is deliberately several years old:
# nginx 1.20 was current in 2021. It is a real image from the official
# repository, not a booby-trapped teaching sample, and it is exactly
# what you get if you pin a version and then never revisit it.
docker pull nginx:1.20
How you know it worked:
# The image is local now. Expect a line for nginx with tag 1.20.
docker images nginx
Scan it
This command is long, so here is what each part does before you run it. The comments matter more than usual; three of these flags are the difference between a scan and an error.
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v trivycache:/root/.cache \
aquasec/trivy:latest image --scanners vuln nginx:1.20
--rmthrows the scanner container away when it finishes. Without it you accumulate dead containers, as lesson 6.4 showed.-v /var/run/docker.sock:/var/run/docker.socklets the scanner see the images on this machine. This is the docker-group warning from lesson 6.4 made literal: you are handing a container the same socket that is equivalent to root. It is a reasonable trade for a scanner you chose from a known publisher, and it is exactly the decision that lesson taught you to make deliberately rather than by reflex.-v trivycache:/root/.cachekeeps the vulnerability database in a named volume, the concept from lesson 6.5. Without it every scan re-downloads several hundred megabytes.--scanners vulnasks only for package vulnerabilities. Trivy can also hunt for secrets and misconfiguration; leaving those on makes a long report longer while you are still learning to read it.
The first run downloads the vulnerability database, which takes a couple of minutes on a normal connection and prints progress as it goes. Later runs reuse it and take seconds.
What you should see
A wide table, one row per finding, ending in a summary line. The columns that matter:
| Column | What it means |
|---|---|
| Library | the installed package, for example zlib1g |
| Vulnerability | the CVE identifier from lesson 13.1 |
| Severity | the CVSS-derived word: CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN |
| Status | fixed, affected, will_not_fix, fix_deferred |
| Installed Version | what is in the image now |
| Fixed Version | the version that resolves it, when one exists |
How you know it worked: the last line of the output is a summary in this shape, and the numbers should be in the hundreds:
Total: 772 (UNKNOWN: 20, LOW: 228, MEDIUM: 307, HIGH: 184, CRITICAL: 33)
Your numbers will not match mine exactly, and that is expected rather than a problem. Vulnerability databases are updated daily, so a scan of the same image next week finds a slightly different total. The shape is what matters: several hundred findings, a few dozen critical.
If you get Cannot connect to the Docker daemon, that is the group
membership from lesson 6.4 again. Run groups and check docker is listed;
if not, log out and back in.
If the download stalls or fails, the database comes from a public registry and occasionally rate-limits. Wait a minute and run it again. The named volume means you will not lose what already downloaded.
Sit with that number for a second
Several hundred findings. Thirty-odd of them CRITICAL. On one image, running one service, that you pulled from the official repository.
The instinct is that you have done something wrong. You have not. This is what a normal image looks like when it is scanned honestly. A container image is a whole operating system's worth of packages, most of which the application never calls, all of which get compared against every CVE ever filed against them.
Now notice the trap. If you sort by severity and start at the top, you have 33 CRITICAL findings to work through, and you have no idea whether any of them can actually be reached in this image, whether fixes exist, or whether anybody in the world has ever exploited them.
That queue is a full week of work and it is the wrong week's work.
Keep the output
You need this data in the next lesson, so save it as JSON rather than a table:
# -f json changes the output format. > writes it to a file
# instead of your screen.
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v trivycache:/root/.cache \
aquasec/trivy:latest image --scanners vuln -f json nginx:1.20 \
> ~/nginx-1.20-scan.json
How you know it worked:
# A few megabytes of JSON. Expect a size in the low single-digit MB,
# and definitely not 0.
ls -lh ~/nginx-1.20-scan.json
# And that it is valid JSON with findings in it. Expect a number
# in the hundreds, matching your Total line above.
jq '[.Results[].Vulnerabilities[]] | length' ~/nginx-1.20-scan.json
If jq is not installed, sudo apt install -y jq. It is the JSON
equivalent of the text tools from lesson 2.2 and you will use it constantly
from here on.
If that last command prints null or an error, the scan wrote an error
message into the file instead of results. Open it with
head -c 300 ~/nginx-1.20-scan.json and you will see what went wrong.
What you take from this
You ran the accurate kind of scan from lesson 13.1, against a real image, and got several hundred findings that are all technically true and completely unusable as a work queue.
The next lesson turns that pile into a list of four things.