dive & Image Efficiency
Summary
diveis a tool that explores a built image layer by layer and reports wasted space and duplicated files.- Insight: image size is not one number - it is the sum of layers, and a file added in one layer and deleted in a later one still ships in the first.
- Key Concepts: layers, efficiency score, wasted space, the delete-does-not-shrink trap, CI thresholds.
- When to Use: after any Dockerfile change that touches package installs,
COPYorder, or multi-stage boundaries. - Limitations:
divemeasures layout, not correctness or vulnerabilities - pair it with structure tests and scanning.
Recipe
Build the image, then run dive in CI mode with efficiency thresholds.
docker build -t app:ci .
CI=true dive app:cidive reads the local image, computes an efficiency percentage and wasted-space total, and exits non-zero if either breaches your configured limit.
Working Example
Start with a .dive-ci policy file so thresholds live in the repo.
rules:
lowestEfficiency: 0.95
highestWastedBytes: 20MB
highestUserWastedPercent: 0.10Run dive against a freshly built image, pointing it at that policy.
docker build -t app:ci .
dive --ci --ci-config .dive-ci app:ciA common failure looks like this: the build installs a toolchain, then deletes it in a later layer, and dive reports the deleted bytes as wasted.
# Wasteful - build tools remain in an earlier layer
FROM node:22.11.0-bookworm-slim
COPY . /app
WORKDIR /app
RUN apt-get update && apt-get install -y build-essential
RUN npm ci && npm run build
RUN apt-get purge -y build-essential # too late, bytes already committedFix it with a multi-stage build so the toolchain never reaches the final image.
# syntax=docker/dockerfile:1
FROM node:22.11.0-bookworm-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22.11.0-bookworm-slim AS runtime
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
USER 10001
CMD ["node", "dist/server.js"]Now the build-essential layer lives only in the build stage, which is discarded, and dive reports near-100% efficiency.
Deep Dive
How layers accumulate
Each RUN, COPY, and ADD produces a layer that is a diff over the previous filesystem.
A file written in layer 3 and removed in layer 5 still occupies space in layer 3, because layers are immutable and stacked.
The union filesystem only hides the file at runtime; it never reclaims the bytes.
This is why RUN rm after an install does nothing unless the install and the removal share a single RUN.
Reading the efficiency score
dive reports an efficiency percentage: the ratio of bytes that survive to the final image versus total bytes across all layers.
Wasted space is the absolute count of bytes that a later layer deletes or overwrites.
highestUserWastedPercent catches the same waste as a fraction, which scales better across differently sized images.
Ordering COPY for cache reuse
Copy dependency manifests before source so a code change does not invalidate the dependency layer.
COPY package*.json ./
RUN npm ci
COPY . .This does not shrink the image, but it keeps the expensive npm ci layer cached, which cuts build time.
Gotchas
Deleting files in a separate RUN does not shrink the image - combine install and cleanup in one instruction.
dive needs the image present in the local engine; in CI, build first or load the image before running the check.
A high efficiency score with a large base still yields a large image - efficiency is relative, so watch absolute size too.
Bind-mounted secrets copied into a layer stay in that layer even after deletion; use BuildKit --mount=type=secret instead of COPY.
.dockerignore is your cheapest win - excluding .git, node_modules, and test fixtures removes bytes before they ever enter a layer.
Alternatives
docker history --no-trunc app:ci shows per-layer sizes without installing anything, but it does not compute wasted space.
docker build with BuildKit and --squash collapses layers, but squashing defeats cache reuse and is rarely worth it.
Distroless or scratch base images cut size at the source and pair well with multi-stage builds.
slim and similar tools rewrite an image to strip unused files, but they add a fragile step and can break dynamic loading; prefer fixing the Dockerfile.
FAQs
Does dive scan for vulnerabilities? No - it only analyzes layout and waste. Use Trivy or Grype for CVEs.
Can dive run in CI without Docker? It reads OCI images from the local store or a tar, so a Docker daemon is not strictly required if you provide the archive.
Why is my image huge despite a small app? Usually a fat base image or build tools left in an early layer - check with a multi-stage split.
What efficiency target is reasonable? Many teams start at 0.95 and tighten; the absolute wasted-bytes rule matters more than the percentage.
Does squashing help? It removes waste but breaks caching and provenance per layer, so multi-stage builds are the better fix.
Related
- Why Images Need Their Own Quality Gates
- Linting Basics
- Container Structure Tests
- Smoke Tests After Build
- Linting Best Practices
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).