Multi-Stage Builds: Beyond Basics
Multi-stage builds are Docker's answer to the "build vs runtime" dilemma. You've seen the basics - separate build and runtime stages. This article goes deeper: parallel stages, conditional builds, testing stages, and patterns that make complex builds manageable.
π At a Glance
| Aspect | Details |
|---|---|
| Topic | Advanced multi-stage patterns, testing stages, secrets handling |
| Complexity | Intermediate-Advanced |
| Prerequisites | Part 3 (Build Process), Part 5 (Optimization) |
| Key Insight | Stages are independent build graphs - use them for more than size |
| Time to Master | 2-3 hours |
π― What You'll Learn
- Advanced stage patterns - parallel builds, conditional stages, shared bases
- Testing in builds - test stages, fail-fast builds, coverage extraction
- Secrets management - build-time secrets that never reach images
- Dynamic staging - building different outputs from same Dockerfile
- Real-world patterns - monorepo builds, polyglot applications
π₯ Production Story: The Leaked Credentials
.npmrc file after npm install.DOCKERFILE(7 lines)CodeLoading syntax highlighter...
BASH(6 lines)CodeLoading syntax highlighter...
.npmrc was deleted in layer 4, but it still existed in layer 2. Docker layers are additive - deletion only hides, never removes.DOCKERFILE(10 lines)CodeLoading syntax highlighter...
π§ Mental Model: Stages as Build Graphs
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β MULTI-STAGE BUILD GRAPH β β β β Traditional view: Linear pipeline β β βββββββ βββββββ βββββββ βββββββ β β βbuildβ β βtest β β βlint β β βfinalβ β β βββββββ βββββββ βββββββ βββββββ β β β β Reality: Directed Acyclic Graph (DAG) β β β β βββββββββββ β β β base β β Shared foundation β β ββββββ¬βββββ β β β β β ββββββββββββββββ¬βββββββββββββββ β β βΌ βΌ βΌ β β βββββββββββ βββββββββββ βββββββββββ β β β deps β β test β β lint β β Run in parallel! β β ββββββ¬βββββ ββββββ¬βββββ ββββββ¬βββββ β β β β β β β β βΌ βΌ β β β βββββββββββ βββββββββββ β β β β report β β check β β Optional outputs β β β βββββββββββ βββββββββββ β β β β β ββββββββββββββββ¬ββββββββββββββββββ β β βΌ β β βββββββββββ β β β final β β Production image β β βββββββββββ β β β β BuildKit builds independent branches in PARALLEL β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π¬ Deep Dive
Stage Fundamentals Revisited
DOCKERFILE(12 lines)CodeLoading syntax highlighter...
- Each stage starts fresh (empty filesystem)
- Stages can be named (
AS name) or numbered (0, 1, 2...) - Only the final stage becomes the output image
COPY --from=extracts files from other stages- ARG values reset after each FROM
Parallel Stage Execution
BuildKit automatically parallelizes independent stages:
DOCKERFILE(25 lines)CodeLoading syntax highlighter...
Time βββββββββββββββββββββββββββββββββββββββββββββββββββββββββΊ frontend: [npm ci]ββββββ[build]ββββββββββββββββββ β backend: [pip install]β[pytest]βββββββββββββββββΌββββ β β tools: [go build]βββββββββββββββββββββββββββββΌββββΌβββ β β β final: βββββ΄βββ΄β[copy all] Total time = max(frontend, backend, tools) + final copy NOT: frontend + backend + tools + final (sequential)
Shared Base Stages
Avoid duplicating common setup:
DOCKERFILE(31 lines)CodeLoading syntax highlighter...
Testing Stages
DOCKERFILE(26 lines)CodeLoading syntax highlighter...
DOCKERFILE(11 lines)CodeLoading syntax highlighter...
DOCKERFILE(19 lines)CodeLoading syntax highlighter...
BASH(5 lines)CodeLoading syntax highlighter...
Secrets in Multi-Stage Builds
DOCKERFILE(16 lines)CodeLoading syntax highlighter...
BASH(2 lines)CodeLoading syntax highlighter...
DOCKERFILE(15 lines)CodeLoading syntax highlighter...
BASH(2 lines)CodeLoading syntax highlighter...
DOCKERFILE(20 lines)CodeLoading syntax highlighter...
Dynamic Stage Selection
DOCKERFILE(28 lines)CodeLoading syntax highlighter...
BASH(4 lines)CodeLoading syntax highlighter...
Monorepo Patterns
monorepo/ βββ packages/ β βββ common/ β βββ api/ β βββ web/ βββ package.json βββ Dockerfile
DOCKERFILE(38 lines)CodeLoading syntax highlighter...
BASH(3 lines)CodeLoading syntax highlighter...
COPY --from External Images
You can copy from any image, not just build stages:
DOCKERFILE(13 lines)CodeLoading syntax highlighter...
Build Arguments Across Stages
DOCKERFILE(18 lines)CodeLoading syntax highlighter...
BASH(5 lines)CodeLoading syntax highlighter...
β οΈ Common Mistakes
Mistake 1: Not Using Named Stages
DOCKERFILE(13 lines)CodeLoading syntax highlighter...
Mistake 2: Forgetting ARG Scope
DOCKERFILE(10 lines)CodeLoading syntax highlighter...
Mistake 3: Copying More Than Needed
DOCKERFILE(10 lines)CodeLoading syntax highlighter...
π Debug This: The Missing Environment Variable
A developer reports: "My app works in the build stage but fails in production. Environment variables are missing!"
DOCKERFILE(13 lines)CodeLoading syntax highlighter...
ENV variables don't transfer between stages. Each stage is independent.
ENV API_URL was set in builder stage, but the final stage starts fresh from node:20-alpine with no knowledge of the builder's environment.DOCKERFILE(10 lines)CodeLoading syntax highlighter...
DOCKERFILE(9 lines)CodeLoading syntax highlighter...
DOCKERFILE(9 lines)CodeLoading syntax highlighter...
π» Exercises
Exercise 1: Parallel Stage Build
β Difficulty: Easy | β±οΈ Time: 15 minutes
DOCKERFILE(8 lines)CodeLoading syntax highlighter...
BASH(16 lines)CodeLoading syntax highlighter...
Exercise 2: Test Stage with Artifacts
ββ Difficulty: Medium | β±οΈ Time: 20 minutes
BASH(19 lines)CodeLoading syntax highlighter...
Exercise 3: Secret Handling
ββ Difficulty: Medium | β±οΈ Time: 20 minutes
BASH(15 lines)CodeLoading syntax highlighter...
Exercise 4: Monorepo Multi-Service Build
βββ Difficulty: Hard | β±οΈ Time: 30 minutes
BASH(22 lines)CodeLoading syntax highlighter...
Exercise 5: Complete CI/CD Pipeline Dockerfile
ββββ Difficulty: Expert | β±οΈ Time: 35 minutes
Create a comprehensive Dockerfile with these stages:
deps-base β deps-dev β lint βββ β β deps-prod βββ build β production β test ββββββββββββ β coverage (extractable)
Requirements:
deps-base: Install system depsdeps-dev: Full dev dependenciesdeps-prod: Production onlylint: ESLint check (fails build if errors)test: Jest tests (fails build if tests fail)coverage: Extractable coverage reportbuild: TypeScript compileproduction: Minimal runtime image
BASH(8 lines)CodeLoading syntax highlighter...
π€ Senior-Level Interview Questions
Q1: How do you handle secrets in multi-stage Docker builds?
"There are several patterns depending on the use case:
DOCKERFILECodeLoading syntax highlighter...
--secret id=npmrc,src=.npmrc.DOCKERFILE(7 lines)CodeLoading syntax highlighter...
DOCKERFILECodeLoading syntax highlighter...
- Don't use ARG for secrets in final stage (visible in history)
- Don't COPY secret files (persist in layers)
- Don't delete secrets after use in same stage (still in earlier layer)
The key principle: secrets should exist only during specific RUN commands, never in any layer that becomes part of the final image."
Q2: Explain how BuildKit parallelizes multi-stage builds.
"BuildKit represents the Dockerfile as a directed acyclic graph (DAG) and executes independent branches in parallel.
- Parses all stages and their dependencies (COPY --from, FROM...AS)
- Builds dependency graph
- Stages with no dependencies on each other run concurrently
DOCKERFILE(9 lines)CodeLoading syntax highlighter...
Without parallelism: 30 + 30 + copy = 61s With parallelism: max(30, 30) + copy = 31s
- Keep stages independent when possible
- Use shared base stages to avoid duplicate work
- Don't create unnecessary dependencies between stages
BASHCodeLoading syntax highlighter...
Shows which stages run simultaneously.
Q3: When would you use multiple FROM stages vs just combining RUN commands?
"They solve different problems:
- Size reduction - Build tools not needed at runtime
- Security isolation - Secrets in build stage only
- Different base images - Full SDK for build, minimal for runtime
- Parallel builds - Independent stages run concurrently
- Multiple outputs - Same Dockerfile, different --target builds
- Layer optimization - Install and clean in one layer
- Cache efficiency - Logically grouped operations
- Simple linear operations - No benefit from separation
Need different base image? β Multi-stage Handling secrets? β Multi-stage Want parallel builds? β Multi-stage Building multiple artifacts? β Multi-stage Just installing packages? β Combined RUN Simple cleanup? β Combined RUN
DOCKERFILE(7 lines)CodeLoading syntax highlighter...
I use multi-stage as the default for production images and combine RUNs within each stage for optimization."
Q4: How would you structure a Dockerfile for a monorepo with shared dependencies?
"Monorepo Dockerfiles need to balance build speed, parallelism, and caching:
DOCKERFILE(14 lines)CodeLoading syntax highlighter...
- Layer caching - Copy package.jsons first, then source
- Shared code - Build shared libraries first, copy to dependents
- Parallelism - Independent services build in parallel
- Selective builds - Use --target to build specific services
- Cache mounts - Share npm/pip cache across stages
- Consider using Docker buildx bake for coordinated builds
- Use build matrix in CI for service-specific builds
- Implement change detection to skip unchanged services
Q5: How do you debug issues in earlier build stages when only the final stage fails?
"Several techniques:
BASH(2 lines)CodeLoading syntax highlighter...
DOCKERFILE(2 lines)CodeLoading syntax highlighter...
BASHCodeLoading syntax highlighter...
Shows all output including from cached layers.
DOCKERFILE(3 lines)CodeLoading syntax highlighter...
BASHCodeLoading syntax highlighter...
DOCKERFILE(4 lines)CodeLoading syntax highlighter...
BASHCodeLoading syntax highlighter...
BASHCodeLoading syntax highlighter...
My usual flow: build with --progress=plain, identify failing step, build --target to that stage, shell in and investigate."
π Summary & Key Takeaways
Core Concepts
| Concept | Key Point |
|---|---|
| Stage independence | Each stage starts fresh, shares nothing automatically |
| Parallel execution | Independent stages build concurrently |
| Named stages | Use AS name for maintainability |
| COPY --from | Only way to transfer files between stages |
| --target | Build up to specific stage |
Multi-Stage Patterns
DOCKERFILE(14 lines)CodeLoading syntax highlighter...
What You Can Do Now
- Structure builds for parallelism - Independent stages for speed
- Handle secrets safely - BuildKit mounts or stage isolation
- Create multi-output Dockerfiles - One file, many targets
- Debug intermediate stages - --target and artifact extraction
π Quick Reference
Stage Syntax
DOCKERFILE(16 lines)CodeLoading syntax highlighter...
BuildKit Secret/SSH
DOCKERFILE(13 lines)CodeLoading syntax highlighter...
Debug Commands
BASH(11 lines)CodeLoading syntax highlighter...
π Review Schedule
| Day | Task | Time |
|---|---|---|
| Day 1 | Review stage isolation principles | 10 min |
| Day 3 | Do Exercise 1 (parallel stages) | 15 min |
| Day 7 | Implement testing stage in a project | 25 min |
| Day 14 | Add BuildKit secrets to a build | 20 min |
| Day 30 | Refactor a complex Dockerfile with multiple targets | 30 min |
π Series Navigation
| Previous | Current | Next |
|---|---|---|
| Part 5: Dockerfile Optimization | Part 6: Multi-Stage Builds | Part 7: Base Image Selection |
- Part 0: How to Use This Series
- Part 1: Container Internals
- Part 2: Image Anatomy
- Part 3: Build Process Deep Dive
- Part 4: Networking Internals
- Part 5: Dockerfile Optimization Patterns
- Part 6: Multi-Stage Builds: Beyond Basics β You are here
- Part 7: Base Image Selection & Security