This chapter distils the book into a prioritised, repeatable checklist. Apply it to any existing project — or use it as a baseline for every new one.
Before changing anything, measure what you have. You cannot improve what you have not measured.
docker images and note the current sizedocker history --no-trunc <image> and identify the largest layersdive <image> and note the efficiency score and wasted spacedocker scout cves <image> and note the CVE count by severityhadolint Dockerfile and note any warningsThese four changes deliver the most size reduction for the least effort. Apply them in this order.
Replace the full base image with a slim or alpine variant.
FROM line)python:3.x → python:3.x-slim-bookwormnode:xx → node:xx-alpineopenjdk:xx → eclipse-temurin:xx-jre-alpine.dockerignore file.dockerignore at the repo root.git, __pycache__, *.pyc, .env, tests/, docs/, *.md, node_modules/RUN instructionsrm -rf, apt-get clean, etc.) that is in a separate RUN from the installRUN as its corresponding install using &&builder stage that installs build tools and produces the runtime artifactruntime stage based on the minimal base imageCOPY --from=builder to copy only the artifact--no-install-recommends to all apt-get install commands--no-cache-dir to all pip install commands (or use BuildKit cache mounts)--no-cache to all apk add commands--omit=dev to production npm ci commandsrequirements.txt, package.json) are copied before application sourceUSER nobody (or a dedicated non-root user) before the final CMD/ENTRYPOINTENV or ARG that contain secretshadolint as a pre-build step in CIdive --ci as a post-build step in CIdocker scout cves with --only-severities critical,high as a gatecheck_image_size.py with a size budget appropriate for your runtimetype=gha for GitHub Actions)sha-<commit> image tags in production deploymentsApply after the baseline optimisations are in place.
Python:
.pyc files with python -m compileallNode.js:
npm ci --omit=dev in runtime stagenode_modules entirely at runtimeGo:
CGO_ENABLED=0 -ldflags="-s -w" in build commandFROM scratch if no shell is needed; add ca-certificates if making HTTPS callsJava:
jlink for a custom minimal JREUse these as benchmarks. If your image is larger, there is recoverable size.
| Runtime | Realistic target |
|---|---|
| Go (scratch) | 10–25 MB |
| Python (slim + venv) | 70–120 MB |
| Node.js (alpine + prod deps) | 80–180 MB |
| Java (JRE-alpine) | 180–250 MB |
| Java (jlink custom JRE) | 80–150 MB |
| Priority | Technique | Expected reduction |
|---|---|---|
| 1 | Switch to slim/alpine base image | 50–80% |
| 2 | Add multi-stage build | 40–70% |
| 3 | Chain RUN instructions | 5–30% |
| 4 | Add .dockerignore |
Build speed, minor size |
| 5 | Package manager flags | 5–20% |
| 6 | BuildKit cache mounts | Build speed only |
| 7 | Language-specific tuning | 10–30% |
A 500 MB image reduced to 80 MB is not just 420 MB saved on one server. It is 420 MB saved on every pull, on every node, on every deploy, across the entire lifetime of the service. On a fleet of twenty microservices deploying ten times a day to ten nodes, that is 84 GB of data transfer eliminated every day — before accounting for faster cold starts, a smaller CVE surface, and the compound interest of keeping these habits as the fleet grows.
The techniques in this book require a day of work to apply to an existing project and twenty minutes to apply to a new one. The return is indefinite.
| ← Chapter 9: CI/CD Integration | Table of Contents |