Introduction: Why Image Size Matters


Most Docker tutorials end the moment the container runs. You type docker build -t myapp ., wait a minute, see Successfully built, and move on. What the tutorial rarely shows is the image size printed at the end of docker images:

REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
myapp        latest    3f8a2c1d9e4b   2 seconds ago   1.24 GB

One gigabyte for a web server that serves JSON. This is not unusual. It is the default outcome when you inherit a python:3.12 or node:20 base image and install your dependencies on top. And it has real consequences.


The Four Costs of a Fat Image

1. Pull Time and Portability

Every time a node in your cluster starts a new container — on deploy, on scale-out, on failure recovery — it first checks whether the image is already cached locally. If not, it pulls from the registry. A 1.2 GB image takes 30–120 seconds to pull on a typical cloud network connection. A 100 MB image takes 3–10 seconds.

That difference is invisible during a calm Tuesday afternoon. It becomes very visible at 2 AM when your on-call engineer is trying to recover from an incident and every new pod is sitting in ContainerCreating for two minutes.

Portability also suffers. Sharing an image with a colleague, moving it to a new region, or running it in a resource-constrained environment all become slower and more expensive as the image grows.

2. Hosting and Transfer Costs

Container registries charge for storage and, more significantly, for data egress. A deployment pipeline that builds and pushes on every commit will transfer the image’s layer delta to the registry, and then the orchestrator will pull the updated layers to every node that runs the new version.

Consider a modest setup: 500 MB image, 10 deployments per day, 5 nodes pulling each deployment. That is 25 GB of outbound transfer per day. At AWS ECR pricing, egress to the internet runs roughly $0.09 per GB — meaning this single service costs around $2.25/day in transfer fees alone, or about $820/year. Cut the image to 80 MB and that cost drops to $130/year. Across a fleet of twenty microservices, the savings compound quickly.

3. Security Attack Surface

Every package in your image is a potential vulnerability. A full python:3.12 base image ships with gcc, make, curl, wget, git, and dozens of other utilities that are useful for building software but serve no purpose in a running production container. Each of these is a package with a version, and versions accumulate CVEs over time.

The relationship is roughly linear: more packages means more vulnerabilities. A python:3.12-slim image has fewer than 40 packages; a python:3.12 image has over 400. Running docker scout cves python:3.12 against a fresh pull typically shows dozens of known vulnerabilities, many of them critical, that simply do not exist in the slim variant.

Minimising the image minimises the blast radius if a dependency is compromised.

4. Cold-Start Latency

In serverless and autoscaling environments, containers are created and destroyed frequently. The time between “trigger received” and “request handled” — the cold-start latency — includes the time to pull the image, decompress its layers, and start the process. A leaner image reduces this latency directly.

This matters for Kubernetes HPA scale-up events, AWS Lambda container images, Google Cloud Run, and any deployment that relies on rapid horizontal scaling to handle traffic spikes.


How Big Is “Too Big”?

There are no universal rules, but these are reasonable targets for common runtime types:

Runtime Achievable size
Go (statically linked) < 20 MB
Python (slim base + venv) 60–120 MB
Node.js (alpine + prod deps) 80–200 MB
Java (JRE-only + app JAR) 150–250 MB

If your image is significantly larger than these figures, there is almost certainly size to recover — and this book will show you exactly where to look.


A Mental Model for the Rest of the Book

A Docker image is a stack of immutable, read-only layers. Each instruction in your Dockerfile that produces output — RUN, COPY, ADD — creates a new layer. The total image size is the sum of all layer sizes.

There are exactly three ways to reduce that sum:

  1. Start smaller — choose a base image that contains only what you need.
  2. Add less — avoid installing packages and files that the running application does not require.
  3. Never add in the first place — use multi-stage builds to perform build-time work in a separate, disposable stage.

The chapters that follow explore each of these approaches in depth, from the foundational mechanics to the practical techniques and tools that make them routine.


How to Use This Book

Read chapters 1 and 2 first — they establish the mental model that makes everything else make sense. Chapters 3 through 6 can be read in order or consulted as reference. Chapter 7 is structured as independent sections by language; read only the sections relevant to your stack. Chapters 8 and 9 are practical and hands-on. Chapter 10 is the synthesis.

If you are in a hurry, the highest-leverage changes in roughly five minutes of reading are:

Those four changes alone can reduce a typical image by 60–80%.


Table of Contents Chapter 1: Understanding Docker Image Layers →