Docker Multi-Stage Builds¶
Observe a Bloated Single-Stage Build¶
A common mistake is installing build tools in the same image that runs the application. Build tools like compilers and test frameworks are only needed during the build — shipping them in production images wastes disk space and increases the attack surface.
graph TD
SRC["📄 Source Code
+ Build Tools
(gcc, go, npm...)"]-->|single RUN|IMG["📀 Fat Image
(build tools INCLUDED)"]
IMG-->|docker run|CNT["📦 Container
(carries unused build tools)"]
style SRC fill:#f3f4f6,stroke:#9ca3af,stroke-width:2px,color:#1f2937
style IMG fill:#fee2e2,stroke:#ef4444,stroke-width:2px,color:#7f1d1d
style CNT fill:#fee2e2,stroke:#ef4444,stroke-width:2px,color:#7f1d1d
Write a single-stage Dockerfile.
cat > Dockerfile.single << 'EOF'
FROM golang:1.22-alpine
WORKDIR /app
RUN echo 'package main' > main.go && \
echo 'import "fmt"' >> main.go && \
echo 'func main() { fmt.Println("Hello") }' >> main.go
RUN go build -o app main.go
CMD ["./app"]
EOF
Build it by running docker build -t go-single -f Dockerfile.single .
docker build -t go-single -f Dockerfile.single .
[+] Building 3.5s (8/8) FINISHED docker:default
...
=> => naming to docker.io/library/go-single 0.0s
Check its size by running docker images go-single.
docker images go-single
REPOSITORY TAG IMAGE ID CREATED SIZE
go-single latest 1a2b3c4d5e6f 15 seconds ago 256MB
Build and Compare a Multi-Stage Image¶
A multi-stage build uses multiple FROM instructions in a single Dockerfile. Each stage is independent. The AS keyword names a stage. COPY --from=STAGE copies artifacts from one stage into another without carrying over the build environment.
graph TD
SRC["📄 Source Code"]-->|Stage 1: builder
go build|BIN["⚙️ Binary
(compiled)"]
BIN-->|COPY --from=builder|RT["📀 Runtime Image
(alpine only)"]
RT-->|docker run|CNT["📦 Container
(lean, no build tools)"]
style SRC fill:#f3f4f6,stroke:#9ca3af,stroke-width:2px,color:#1f2937
style BIN fill:#ffedd5,stroke:#f59e0b,stroke-width:2px,color:#78350f
style RT fill:#dbeafe,stroke:#3b82f6,stroke-width:2px,color:#1e3a8a
style CNT fill:#dcfce7,stroke:#22c55e,stroke-width:2px,color:#14532d
Write the multi-stage Dockerfile.
cat > Dockerfile << 'EOF'
# --- Stage 1: Build ---
FROM golang:1.22-alpine AS builder
WORKDIR /app
RUN echo 'package main' > main.go && \
echo 'import "fmt"' >> main.go && \
echo 'func main() { fmt.Println("Hello") }' >> main.go
RUN go build -o app main.go
# --- Stage 2: Runtime ---
FROM alpine:3.22
WORKDIR /app
COPY --from=builder /app/app .
CMD ["./app"]
EOF
Build the multi-stage image.
docker build -t go-multi .
[+] Building 4.2s (10/10) FINISHED docker:default
...
=> => naming to docker.io/library/go-multi 0.0s
Run it to verify it works.
docker run --rm go-multi
Hello
Finally, run docker images | grep -E "go-single|go-multi" to compare both image sizes side by side.
docker images | grep -E "go-single|go-multi"
go-single latest 1a2b3c4d5e6f 2 minutes ago 256MB
go-multi latest f1g2h3i4j5k6 15 seconds ago 9.2MB
Observe that go-multi is dramatically smaller because the Go toolchain (golang:1.22-alpine) is not present in the final image — only the compiled binary was copied over.
Build a Specific Stage¶
docker build --target stops the build at a named stage. This is useful for debugging the build environment without producing the full runtime image.
Build only the builder stage.
docker build --target builder -t go-builder-only .
[+] Building 0.2s (7/7) FINISHED docker:default
...
=> => naming to docker.io/library/go-builder-only 0.0s
Verify the Go compiler is present in this intermediate stage.
docker run --rm go-builder-only go version
go version go1.22.5 linux/amd64
Now, try running the same command on your final production image.
docker run --rm go-multi go version
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "go": executable file not found in $PATH: unknown.
It will fail! This proves that the multi-stage build successfully stripped out the massive build tools before producing the final image.
🧠 Quick Quiz¶
What is the primary benefit of using a multi-stage build?
How do you name a specific stage in a multi-stage Dockerfile?
Which command is used to retrieve artifacts from a previous build stage?
Practice Live in Your Browser
Don't just read about Docker commands—execute them in real time! Launch a fully-configured, secure Docker sandbox directly in your browser with automated task validation ready for you.
📬 DevopsPilot Weekly — Learn DevOps, Cloud & Gen AI the simple way.
👉 Subscribe here