Generated using AI. Be aware that everything might not be accurate.



Chapter 10: Action Plan


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.


Phase 1: Audit Your Current Image

Before changing anything, measure what you have. You cannot improve what you have not measured.

  • Run docker images and note the current size
  • Run docker history --no-trunc <image> and identify the largest layers
  • Run dive <image> and note the efficiency score and wasted space
  • Run docker scout cves <image> and note the CVE count by severity
  • Run hadolint Dockerfile and note any warnings
  • Record baseline: image size, CVE count, efficiency score

Phase 2: High-Impact Changes (Do These First)

These four changes deliver the most size reduction for the least effort. Apply them in this order.

2.1 Switch the base image

Replace the full base image with a slim or alpine variant.

  • Identify your current base image (check the FROM line)
  • Replace python:3.xpython:3.x-slim-bookworm
  • Replace node:xxnode:xx-alpine
  • Replace openjdk:xxeclipse-temurin:xx-jre-alpine
  • Run the tests; fix any compatibility issues (most common: missing system packages)
  • Measure the new size

2.2 Add a .dockerignore file

  • Create .dockerignore at the repo root
  • Add: .git, __pycache__, *.pyc, .env, tests/, docs/, *.md, node_modules/
  • Rebuild and verify context size is smaller (watch the “Sending build context” line)

2.3 Chain all RUN instructions

  • Find every cleanup command (rm -rf, apt-get clean, etc.) that is in a separate RUN from the install
  • Merge each cleanup into the same RUN as its corresponding install using &&
  • Rebuild and measure

2.4 Add a multi-stage build

  • Identify what build tools are in your runtime image (gcc, make, npm dev deps, Maven, etc.)
  • Create a builder stage that installs build tools and produces the runtime artifact
  • Create a runtime stage based on the minimal base image
  • Use COPY --from=builder to copy only the artifact
  • Rebuild and measure — expect 40–70% reduction

Phase 3: Medium-Impact Changes

3.1 Package manager hygiene

  • Add --no-install-recommends to all apt-get install commands
  • Add --no-cache-dir to all pip install commands (or use BuildKit cache mounts)
  • Add --no-cache to all apk add commands
  • Add --omit=dev to production npm ci commands

3.2 COPY ordering

  • Verify that dependency manifests (requirements.txt, package.json) are copied before application source
  • Verify that the most-frequently-changed files are at the bottom of the Dockerfile

3.3 Security hygiene

  • Add USER nobody (or a dedicated non-root user) before the final CMD/ENTRYPOINT
  • Remove any ENV or ARG that contain secrets
  • Pin the base image to a specific digest for reproducible builds

Phase 4: CI/CD Automation

  • Add hadolint as a pre-build step in CI
  • Add dive --ci as a post-build step in CI
  • Add docker scout cves with --only-severities critical,high as a gate
  • Add check_image_size.py with a size budget appropriate for your runtime
  • Configure BuildKit caching (type=gha for GitHub Actions)
  • Use sha-<commit> image tags in production deployments

Phase 5: Language-Specific Tuning

Apply after the baseline optimisations are in place.

Python:

  • Use virtualenv copy pattern in multi-stage build
  • Pre-compile .pyc files with python -m compileall
  • Verify Alpine is not causing musl/glibc issues for C-extension packages

Node.js:

  • Confirm npm ci --omit=dev in runtime stage
  • Consider bundling to eliminate node_modules entirely at runtime

Go:

  • Confirm CGO_ENABLED=0 -ldflags="-s -w" in build command
  • Use FROM scratch if no shell is needed; add ca-certificates if making HTTPS calls

Java:

  • Switch from JDK to JRE base image
  • Evaluate jlink for a custom minimal JRE
  • Evaluate GraalVM native image for cold-start-sensitive deployments

Size Targets Reference

Use 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

Quick Reference: Optimisation Priority

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%

Resources


Closing Thought

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


>> You can subscribe to my mailing list here for a monthly update. <<