Chapter 8: Analyzing Images: dive, docker scout, hadolint


Optimising a Docker image without measurement is guesswork. You need tools that show you where the bytes are, which packages carry CVEs, and whether your Dockerfile follows best practices. This chapter covers four tools that form a practical auditing workflow: dive, docker scout, hadolint, and grype/syft.


dive — Layer Explorer

dive is an interactive terminal UI that shows you the filesystem contents of each layer, which files changed between layers, and a wasted-space analysis. It is the most direct tool for answering “where is all this space coming from?”

Installation

# Single binary download (Linux)
wget -q https://github.com/wagoodman/dive/releases/latest/download/dive_linux_amd64.tar.gz \
  -O - | tar xz && mv dive /usr/local/bin/

# Or run without installing:
docker run --rm -it \
  -v /var/run/docker.sock:/var/run/docker.sock \
  wagoodman/dive:latest myapp:latest

Interactive usage

dive myapp:latest

The UI is split into three panels:

Navigate with arrow keys; press Tab to switch panels; press Ctrl+U to toggle showing only changed files.

The Image Efficiency Score at the bottom summarises wasted space as a percentage. A score below 90% usually indicates fixable inefficiency (typically from deleted files in separate layers).

CI mode

dive --ci myapp:latest

In CI mode, dive exits non-zero if the efficiency score falls below a threshold, making it usable as a build gate:

PASS: 98% efficiency (threshold: 90%)

Configure the threshold in .dive-ci.yml:

rules:
  lowestEfficiency: 0.95
  highestWastedBytes: 20MB
  highestUserWastedPercent: 0.05

Full script: code/analyze_image.py — wraps dive --ci and docker scout cves into a single audit report.


docker scout — CVE Scanner and Advisor

docker scout is Docker’s integrated security scanning tool, available as a CLI plugin and in Docker Desktop. It scans images for known CVEs and recommends updated base images.

Installation

# As a Docker CLI plugin:
curl -fsSL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh

Docker Desktop includes it by default.

Common commands

Quick overview:

docker scout quickview myapp:latest

Prints a summary table: total CVEs by severity (critical, high, medium, low), base image name, and a prompt to upgrade if a better base exists.

Full CVE list:

docker scout cves myapp:latest

Lists every CVE with severity, package name, and a fix recommendation. Filter by severity:

docker scout cves --only-severity critical,high myapp:latest

Base image recommendations:

docker scout recommendations myapp:latest

Compares the current base image against available alternatives and shows which switch would eliminate the most CVEs.

Compare two images:

docker scout compare myapp:v1 myapp:v2

Shows the diff in CVEs and layer changes between two versions — useful for validating that a base image upgrade actually reduced the CVE count.

Interpreting results

A typical audit of python:3.12 shows 40–80 CVEs including several critical ones inherited from the Debian base. Running the same audit on python:3.12-slim-bookworm typically shows 5–15 CVEs, none critical. This delta is the direct result of fewer installed packages.


hadolint — Dockerfile Linter

hadolint is a Dockerfile linter that checks your Dockerfile against a set of best practices and shell script correctness rules. It catches mistakes that are easy to miss in review: unpinned package versions, unnecessary apt-get patterns, and shell quoting errors.

Installation

docker run --rm -i hadolint/hadolint < Dockerfile
# or
brew install hadolint          # macOS
apt-get install hadolint        # Debian

Usage

hadolint Dockerfile

Example output:

Dockerfile:5 DL3008 warning: Pin versions in apt get install.
Dockerfile:8 DL3013 warning: Pin versions in pip.
Dockerfile:12 SC2086 warning: Double quote to prevent globbing.

Each diagnostic has a code:

Useful rules to know

Code Message
DL3008 Pin versions in apt-get install
DL3009 Delete apt-get lists after installing
DL3013 Pin versions in pip install
DL3020 Use COPY instead of ADD for files
DL3025 Use double quotes in CMD/ENTRYPOINT
DL3042 Avoid pip cache directory (use –no-cache-dir)

Inline ignores

For rules you intentionally want to break:

# hadolint ignore=DL3008
RUN apt-get install -y build-essential

CI integration

- name: Lint Dockerfile
  run: hadolint Dockerfile

hadolint exits non-zero on any finding above the configured threshold, making it a clean CI gate.


grype and syft — Open-Source Scanning

docker scout requires a Docker Hub account. For teams preferring open-source tooling or working in air-gapped environments, syft + grype is the equivalent workflow.

syft generates a Software Bill of Materials (SBOM) — a structured inventory of every package in the image:

syft myapp:latest -o json > sbom.json

grype scans an image (or an SBOM) against vulnerability databases:

grype myapp:latest
# or scan the SBOM:
grype sbom:sbom.json

Both are single binaries, fast, and require no account. grype uses the same vulnerability databases as many commercial scanners (NVD, GitHub Advisory Database, etc.).


Putting It Together

A practical audit workflow before pushing any image:

1. hadolint Dockerfile            → catch Dockerfile mistakes before building
2. docker buildx build ...        → build the image
3. dive --ci myapp:latest         → check layer efficiency (>90% is good)
4. docker scout cves myapp:latest → check for CVEs
5. check_image_size.py myapp:latest 200000000  → gate on size (200 MB here)

Full script: code/analyze_image.py automates steps 3–5 into a single command.


Key Takeaways


← Chapter 7: Language-Specific Optimizations Table of Contents Chapter 9: CI/CD Integration →