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 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?”
# 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
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).
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 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.
# As a Docker CLI plugin:
curl -fsSL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh
Docker Desktop includes it by default.
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.
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 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.
docker run --rm -i hadolint/hadolint < Dockerfile
# or
brew install hadolint # macOS
apt-get install hadolint # Debian
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:
DL rules are Docker-specific (from the hadolint rules list)SC rules come from ShellCheck, which hadolint embeds| 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) |
For rules you intentionally want to break:
# hadolint ignore=DL3008
RUN apt-get install -y build-essential
- name: Lint Dockerfile
run: hadolint Dockerfile
hadolint exits non-zero on any finding above the configured threshold, making it a clean CI gate.
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.).
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.
dive shows which layers contain wasted space and gives a quantified efficiency score.docker scout reports CVEs by severity and recommends base image upgrades.hadolint lints Dockerfiles against best practices and shell correctness rules before you build.grype + syft are open-source alternatives to docker scout for air-gapped or account-free environments.| ← Chapter 7: Language-Specific Optimizations | Table of Contents | Chapter 9: CI/CD Integration → |