DevOps Classroom notes 01/june/2026

Docker Multi-Stage Builds

Multi-Stage Build

A multi-stage build uses multiple FROM instructions in a single Dockerfile. Each FROM begins a new stage — an isolated build environment. You selectively copy only the artifacts you need from one stage into the next.

Dockerfile with multiple FROM statements
        │
        ▼
┌───────────────────────────────────────────────┐
│  Stage 1 (builder)   Stage 2 (tester)         │
│  - Full compiler     - Run tests               │
│  - Build tools       - Generate reports        │
│  - Dev dependencies  ↓                        │
│         ↓           artifacts                  │
│  Stage 3 (production)                          │
│  - Only runtime                                │
│  - Only compiled binary/assets                 │
│  - Tiny, secure image                          │
└───────────────────────────────────────────────┘

Key directive: COPY --from=<stage_name_or_index> <src> <dest>


2. The Problem Without Multi-Stage Builds

Single Dockerfile (❌ Fat Image)

FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm install              # includes devDependencies
COPY . .
RUN npm run build
# Everything above is baked in — compilers, source, node_modules
CMD ["node", "dist/index.js"]

Two-Dockerfile Approach (❌ Complex Scripts)

# build.sh — fragile shell glue
docker build -f Dockerfile.build -t myapp-build .
docker create --name extract myapp-build
docker cp extract:/app/dist ./dist
docker rm -f extract
docker build -f Dockerfile.prod -t myapp:prod .

Problems with both approaches:

Issue Single Dockerfile Two Dockerfiles
Image size Huge (GBs) Small ✓
Complexity Low ✓ High
Build toolchain leaked Yes No ✓
Source code in image Yes No ✓
CI/CD friendly Somewhat Hard
Single file Yes ✓ No

Solution: Multi-Stage Builds (✅ Best of Both)


3. Core Architecture & Flow

High-Level Flow

flowchart TD
    A[docker build .] --> B{Parse Dockerfile}
    B --> C[Stage 1: Builder]
    C --> C1[Install compilers & tools]
    C1 --> C2[Copy source code]
    C2 --> C3[Compile / Build artifacts]
    C3 --> D[Stage 2: Tester]
    D --> D1[Copy build artifacts]
    D1 --> D2[Run unit tests]
    D2 --> D3{Tests pass?}
    D3 -- No --> E[❌ Build fails]
    D3 -- Yes --> F[Stage 3: Production]
    F --> F1[Start from slim base]
    F1 --> F2[COPY --from=builder artifacts only]
    F2 --> F3[Set runtime config]
    F3 --> G[✅ Final Image]
    G --> H[Push to Registry]

What Gets Included vs Excluded

flowchart LR
    subgraph builder["Stage 1 — Builder (discarded)"]
        B1[gcc / go / maven / npm]
        B2[Source code .go .ts .java]
        B3[Dev dependencies]
        B4[Test files]
        B5[Build scripts]
    end

    subgraph copy["COPY --from=builder"]
        C1[compiled binary]
        C2[/dist static assets/]
        C3[.jar file]
    end

    subgraph prod["Stage 2 — Production (kept ✅)"]
        P1[Slim base OS]
        P2[Runtime only]
        P3[Your artifact]
        P4[Config files]
    end

    builder --> |only these| copy
    copy --> prod

    style builder fill:#ffe3e3
    style prod fill:#d3f9d8
    style copy fill:#fff3bf

4. Anatomy of a Multi-Stage Dockerfile

# ─────────────────────────────────────────────
# STAGE 1: Builder
# ─────────────────────────────────────────────
FROM golang:1.22 AS builder        # ← Named stage (use in COPY --from)
WORKDIR /app

# Copy dependency manifests first (better caching)
COPY go.mod go.sum ./
RUN go mod download                # ← Cached unless go.mod changes

# Copy source and compile
COPY . .
RUN go build -o /bin/server ./cmd/server

# ─────────────────────────────────────────────
# STAGE 2: Production
# ─────────────────────────────────────────────
FROM gcr.io/distroless/static AS production  # ← New base, blank slate

# Pull ONLY the binary from builder
COPY --from=builder /bin/server /server      # ← Magic line

EXPOSE 8080
ENTRYPOINT ["/server"]

Key Directives Explained

Directive Syntax Purpose
Named stage FROM image AS name Give a stage a reusable name
Copy from stage COPY --from=name src dst Pull files from another stage
Copy from index COPY --from=0 src dst Reference stage by position (fragile)
Copy from image COPY --from=nginx:alpine /etc/nginx.conf / Pull from any public image
Build target docker build --target name Stop build at a specific stage

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Please turn AdBlock off
Social Media Icons Powered by Acurax Web Design Company

Discover more from Direct DevOps from Quality Thought

Subscribe now to keep reading and get access to the full archive.

Continue reading

Visit Us On FacebookVisit Us On LinkedinVisit Us On Youtube