How to Use Docker Multi-Stage Builds to Reduce Image Size
In this tutorial, you'll learn about How to Use Docker Multi. We cover key concepts, practical examples, and best practices.
The Problem
Your Docker image is 1.5 GB because it includes the entire SDK, build tools, and intermediate artifacts. Production containers only need the compiled binary or runtime files, not the entire toolchain used to create them. Multi-stage builds let you use separate FROM statements in a single Dockerfile, copying only the build output into a minimal final image.
Quick Fix
1. Basic multi-stage build for a Go app
# Stage 1: Build
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o myapp .
# Stage 2: Runtime
FROM alpine:3.20
RUN apk --no-cache add ca-certificates
COPY --from=builder /app/myapp /myapp
CMD ["/myapp"]
The final image contains only the binary and Alpine — no Go compiler, no source code, no intermediate object files.
2. Multi-stage build for a Node.js app
# Stage 1: Build frontend
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Serve with nginx
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
The final nginx image is ~25 MB instead of the ~400 MB Node.js base image.
3. Build and compare image sizes
docker build -t myapp:latest .
docker images myapp
Expected output:
REPOSITORY TAG IMAGE ID CREATED SIZE
myapp latest a1b2c3d4e5f6 10 seconds ago 23.4MB
Without multi-stage, the same Go app would be ~1.1 GB. Use docker images to compare before and after.
4. Use scratch for static binaries
FROM golang:1.22 AS builder
RUN CGO_ENABLED=0 go build -o /server .
FROM scratch
COPY --from=builder /server /server
CMD ["/server"]
The scratch base image is completely empty — the image size equals the binary size plus metadata.
5. Name build stages for clarity
FROM python:3.12-slim AS base
FROM base AS dependencies
RUN pip install --no-cache-dir -r requirements.txt
FROM base AS production
COPY --from=dependencies /usr/local /usr/local
COPY . /app
Stage names make multi-stage builds self-documenting.
6. Copy specific files between stages
COPY --from=builder --chown=app:app /app/dist /app
COPY --from=builder --chmod=755 /app/binary /app/
Inspect Container Configuration
docker inspect <container-id> --format '{{json .Config}}' | python3 -m json.tool
# {
# "Hostname": "abc123",
# "Env": ["PATH=/usr/local/bin:..."],
# "Cmd": ["node", "app.js"]
# }
Use docker inspect to examine the full configuration of a container. This reveals misconfigurations in environment variables, command arguments, and network settings that may not appear in logs.
Prevention
- Always use a minimal base image for the final stage (
alpine,scratch,distroless) - Copy only artifacts needed at runtime — no source code, no build tools
- Use
.dockerignoreto excludenode_modules,.git, and build caches from the build context - Tag intermediate stages with meaningful names (
AS <a href="/design-patterns/builder/">builder</a>,AS dependencies) - Build with
docker build --target productionto stop at a specific stage for debugging
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro