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 comment

Turn on JavaScript to leave a comment.

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

Please turn AdBlock off
Social Network Integration by Acurax Social Media Branding Company
Exit mobile version
%%footer%%