Multi-stage Docker Builds: Lean Images, Fast Deploys
Build your app in one container stage and run it in another, separate one. This keeps your final Docker image lean by shipping only the compiled artifact, not the entire build environment, compilers, and source code.
WHY IT EXISTS: Before multi-stage builds, creating lean Docker images was complex. Developers often included build tools, compilers, and source code in the final image, leading to bloat. This increased security risks and slowed down deployments, as large images took longer to pull.
THE MENTAL MODEL: Think of a kitchen with a messy prep area and a clean serving area. The "builder" stage is the prep area, where you use all your tools (compilers, SDKs) and ingredients (source code). The final stage is the serving area. You only move the finished dish (the compiled application) to the serving area, leaving all the mess behind.
HOW IT WORKS: A single Dockerfile uses multiple FROM instructions. Each FROM starts a new build stage, which can be named (e.g., FROM golang:1.21 AS builder). You perform build steps like compiling code in this first stage. Then, you start a new, minimal stage (e.g., FROM alpine:latest) and use the COPY --from=builder command to copy only the necessary artifacts, like the compiled binary, from the builder stage into your final image. Only the last stage becomes the final image; all previous stages are discarded.
WHEN TO USE IT: This pattern is ideal for compiled languages like Go, Rust, Java, or C++, where you can compile in a full SDK stage and copy the binary to a minimal base like scratch or alpine. It's also perfect for frontend applications, where you can use a Node.js stage to build static assets and then serve them from a lightweight web server image like Nginx.
WHEN NOT TO USE IT: For simple applications using interpreted languages like Python, where the source code is the application and there's no separate compilation step, the benefits are smaller. If your build and runtime environments must be identical for debugging or dynamic operations, a single-stage build might be simpler, though this is often an anti-pattern for production.
ONE CANONICAL EXAMPLE: For a Go application, the first stage would use FROM golang:1.21 AS builder to compile the source code into a single binary. The second stage would start with FROM scratch (an empty image) and use COPY --from=builder /path/to/app /app to place only the compiled binary into the final image. This can shrink an image from over 1GB to under 10MB.
Read the original → docs.docker.com
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.