How to Reduce Docker Image Size With Multi-Stage Builds

A multi-stage Dockerfile shrinks bloated images by keeping build-time intermediates - compiled code, asset bundles, downloaded deps - in a disposable builder stage. Only the files the runtime actually needs end up in the final image.

DevOps Engineerdockerdockerfilemulti-stage

Why single-stage Dockerfiles get bloated

A single-stage build accumulates everything in one image: the base OS, build tools, intermediate artifacts, and the finished app. Every RUN layer that creates a large file adds to the final image size - even if you delete the file in a later layer, Docker keeps the data in the earlier layer's snapshot.

A 200MB preprocessing artifact generated at build time stays in the image forever in a single-stage build. On a team doing many deploys a day, that translates to minutes of pull time on every rollout.

The multi-stage pattern

Introduce a named builder stage with AS builder. The second FROM starts a fresh image - it inherits no layers from the builder. Use COPY --from=builder to pull only the outputs you actually need.

# Stage 1 - builder: compile, bundle, generate assets, install build deps
FROM python:3.12-slim AS builder
WORKDIR /build
RUN dd if=/dev/urandom of=/build-artifact.bin bs=1M count=200 2>/dev/null \
 && sha256sum /build-artifact.bin > /build/manifest.txt

# Stage 2 - runtime: starts fresh, pulls only what it needs from stage 1
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /build/manifest.txt ./manifest.txt
COPY app.py .
CMD ["python", "app.py"]

The 200MB /build-artifact.bin lives only in the builder layer. The runtime image contains python:3.12-slim plus manifest.txt and app.py - nothing else. Image drops from ~370MB to ~150MB without touching the app code.

Measure before and after

# Build and check size
docker build -t myapp .
docker images myapp

# Inspect layers to see where bytes come from
docker history myapp

docker images shows the compressed repository size. docker history breaks it down layer by layer - useful for spotting which RUN step is the biggest offender before you start optimizing.

Common real-world variants

Why image size matters beyond deploy speed

Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.

Try it in a workspace →

What you'll practice

FAQ

How does a multi-stage Docker build reduce image size?

Each FROM starts a fresh filesystem. Build-time intermediates - compilers, asset bundles, downloaded artifacts - stay in the builder stage and are never copied to the runtime stage unless you explicitly use COPY --from=builder. The final image only contains what the runtime stage holds.

What is COPY --from in a Dockerfile?

COPY --from=<stage> copies files from a named build stage into the current stage. You use it to pull just the compiled binary, built assets, or manifest you need out of the builder, leaving everything else behind.

How do I check my Docker image size after optimizing?

Run docker images <tag> to see the compressed size, and docker history <tag> to break it down layer by layer so you can find which step contributes the most bytes.

Why is my Docker image so large?

Usually build tools, caches, and dev dependencies baked into the final image, plus a heavy base. Use a multi-stage build so only runtime artifacts ship, pick a slim or alpine base, and clean package caches in the same layer.

Keep learning

Fix the Nginx 502 Bad GatewayDevOps projectRecover a Crashed Linux ServiceDevOps projectCorrect a Postgres Port MismatchDevOps projectDevOps roadmapStep by step to hiredDevOps interview questionsSTAR answersAll DevOps projectsProjects hub

Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.

Open the workspace →