Course outline · 0% complete

0/29 lessons0%

Course overview →

Image size hygiene

lesson 6-2 · ~12 min · 18/29

Why size matters

docker images on a beginner's machine often shows apps weighing 1.2 GB, and size is not only disk space.

Slow ships. Every deploy and every CI run pulls the image, and gigabytes across many machines and many deploys a day adds up to real minutes and real money.

Attack surface. A full OS image ships hundreds of packages the app never uses, and every one of them can carry security vulnerabilities that now need patching.

The first fix is choosing a smaller base in the FROM line.

Base imageRough sizeTrade
python:3.12about 1.0 GBeverything included
python:3.12-slimabout 150 MBno build tools or docs
python:3.12-alpineabout 50 MBminimal libc, occasional breakage

slim drops build tools and documentation. alpine uses a minimal Linux distribution, which is tiny but occasionally incompatible with packages that expect the standard C library. slim is the safe default, which is why the Dockerfile in lesson 3-1 used it.

Putting numbers on the size argument

At 50 deploys a month, the difference between a full and a slim image is easy to quantify with integer arithmetic.

full=1000
slim=150
deploys=50
echo "full: $((full * deploys)) MB"
echo "slim: $((slim * deploys)) MB"
echo "saved: $(((full - slim) * deploys)) MB"

Output

full: 50000 MB
slim: 7500 MB
saved: 42500 MB
ImageMonthly transfer
full, 1000 MB50000 MB
slim, 150 MB7500 MB
difference42500 MB

$(( )) is bash arithmetic expansion, and variables inside it need no $ prefix. The savings line multiplies the size difference by the deploy count, which is about 42 GB of transfer avoided per month for a one-line change to the FROM instruction.

Multi-stage builds

Compiled languages have a worse problem: building needs the whole toolchain (compiler, headers, caches), but running needs only the final binary. A multi-stage build uses two FROMs in one Dockerfile:

FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN go build -o /out/server .

FROM alpine:3.20
COPY --from=builder /out/server /server
CMD ["/server"]
  • FROM golang:1.22 AS builder: stage one, named builder, has the full ~800 MB Go toolchain
  • RUN go build ...: compile to a single binary
  • FROM alpine:3.20: stage two starts fresh from a ~8 MB base
  • COPY --from=builder ...: reach back into stage one and take only the binary

Only the final stage becomes the shipped image. The toolchain, source, and caches are all left behind. Result: ~15 MB instead of ~900 MB.

same app, three ways to build the image~900 MBFROM golang:1.22 (toolchain ships with the app)~150 MB · slim base~15 MB · multi-stage, binary onlysmaller image = faster pulls in CI and deploys, fewer packages to patch
The same application shipped three ways. Multi-stage builds keep the build toolchain out of the final image entirely.

Why the toolchain does not ship

The Go toolchain stays out of the shipped image because only the final FROM stage becomes the image, and it copies just the compiled binary out of the builder stage.

Each FROM starts a new stage, and only the last stage is the output image. COPY --from=builder cherry-picks the one artifact worth keeping.

StageContentsShipped
builderGo toolchain, sources, cachesno
final alpinethe compiled binaryyes

Everything else in the builder stage is simply discarded, which is why the technique cuts an image from roughly 900 MB to roughly 15 MB without changing a line of application code.

Naming the two-stage technique

The technique is a multi-stage build, recognizable because the Dockerfile has more than one FROM.

Stage one, written as FROM golang AS builder, does the heavy compiling. Stage two starts from a tiny base and uses COPY --from=builder to take only the binary.

StageBaseJob
onegolang:1.22compile
twoalpine:3.20run

Only the small final stage ships, and the toolchain never leaves the build. The same pattern applies well beyond Go, covering anything with a build step whose tools are not needed at runtime, including bundled JavaScript front ends and compiled Java jars.