Docker
DockerIntermediate

Docker Multi-stage Builds: Optimizing Container Images

DevHub Team
5 min read
DockerContainersDevOpsOptimization

TL;DR

Master Docker multi-stage builds with this comprehensive guide covering optimization techniques, best practices, and real-world examples for creating efficient container images

Docker Multi-stage Builds: Optimizing Container Images

Multi-stage builds are a powerful feature in Docker that allows you to create efficient and secure container images by separating build-time dependencies from runtime environments. This guide explores best practices and implementation patterns for multi-stage builds.

$1

``mermaid

graph LR

subgraph "Stage 1: Build"

A[Source Code]

B[Build Tools]

C[Dependencies]

D[Compiled App]

end

subgraph "Stage 2: Runtime"

E[Base Image]

F[Runtime Deps]

G[Final App]

end

A --> B

B --> C

C --> D

D --> G

E --> F

F --> G

classDef build fill:#1a73e8,stroke:#fff,color:#fff

classDef runtime fill:#34a853,stroke:#fff,color:#fff

class A,B,C,D build

class E,F,G runtime

`

$1

$1

`dockerfile

Stage 1: Build

FROM node:16-alpine AS builder

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

RUN npm run build

Stage 2: Runtime

FROM node:16-alpine

WORKDIR /app

COPY --from=builder /app/dist ./dist

COPY --from=builder /app/package*.json ./

RUN npm ci --only=production

EXPOSE 3000

CMD ["npm", "start"]

`

$1

`dockerfile

Stage 1: Build

FROM golang:1.20-alpine AS builder

WORKDIR /app

COPY go.* ./

RUN go mod download

COPY . .

RUN CGO_ENABLED=0 GOOS=linux go build -o server .

Stage 2: Runtime

FROM alpine:3.17

RUN apk add --no-cache ca-certificates

COPY --from=builder /app/server /server

EXPOSE 8080

CMD ["/server"]

`

$1

$1

`dockerfile

Stage 1: Test

FROM node:16-alpine AS tester

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

RUN npm run test

Stage 2: Build

FROM node:16-alpine AS builder

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

RUN npm run build

Stage 3: Runtime

FROM node:16-alpine

WORKDIR /app

COPY --from=builder /app/dist ./dist

COPY --from=builder /app/package*.json ./

RUN npm ci --only=production

EXPOSE 3000

CMD ["npm", "start"]

`

$1

`dockerfile

Stage 1: Build Frontend

FROM node:16-alpine AS frontend-builder

WORKDIR /app

COPY frontend/package*.json ./

RUN npm ci

COPY frontend .

RUN npm run build

Stage 2: Build Backend

FROM golang:1.20-alpine AS backend-builder

WORKDIR /app

COPY backend/go.* ./

RUN go mod download

COPY backend .

RUN CGO_ENABLED=0 GOOS=linux go build -o server .

Stage 3: Runtime

FROM alpine:3.17

RUN apk add --no-cache ca-certificates nginx

Copy frontend assets

COPY --from=frontend-builder /app/dist /usr/share/nginx/html

Copy backend binary

COPY --from=backend-builder /app/server /server

EXPOSE 80 8080

CMD ["sh", "-c", "nginx && /server"]

`

$1

$1

Technique Description Impact
Cache Dependencies Copy dependency files first Faster builds
Minimize Layers Combine RUN commands Smaller images
Clean Up Remove build artifacts Reduced size

$1

`dockerfile

Stage 1: Build

FROM node:16-alpine AS builder

ARG NODE_ENV=production

ARG BUILD_FLAG=""

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

RUN npm run build ${BUILD_FLAG}

Stage 2: Runtime

FROM node:16-alpine

ARG NODE_ENV=production

ENV NODE_ENV=${NODE_ENV}

WORKDIR /app

COPY --from=builder /app/dist ./dist

COPY --from=builder /app/package*.json ./

RUN npm ci --only=production

EXPOSE 3000

CMD ["npm", "start"]

`

$1

$1

`dockerfile

Stage 1: Build

FROM python:3.11-slim AS builder

WORKDIR /app

RUN python -m venv /opt/venv

ENV PATH="/opt/venv/bin:$PATH"

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY . .

RUN python setup.py build

Stage 2: Runtime

FROM python:3.11-slim

COPY --from=builder /opt/venv /opt/venv

COPY --from=builder /app/build /app

ENV PATH="/opt/venv/bin:$PATH"

WORKDIR /app

EXPOSE 8000

CMD ["gunicorn", "app:app"]

`

$1

`dockerfile

Stage 1: Build

FROM maven:3.9-eclipse-temurin-17 AS builder

WORKDIR /app

COPY pom.xml .

RUN mvn dependency:go-offline

COPY src ./src

RUN mvn package -DskipTests

Stage 2: Runtime

FROM eclipse-temurin:17-jre-alpine

WORKDIR /app

COPY --from=builder /app/target/*.jar app.jar

EXPOSE 8080

CMD ["java", "-jar", "app.jar"]

`

$1

$1

`yaml

.github/workflows/docker-build.yml

name: Docker Build

on:

push:

branches: [ main ]

pull_request:

branches: [ main ]

jobs:

build:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v2

- name: Set up Docker Buildx

uses: docker/setup-buildx-action@v1

- name: Build and push

uses: docker/build-push-action@v2

with:

context: .

push: false

tags: myapp:latest

cache-from: type=gha

cache-to: type=gha,mode=max

`

$1

$1

Practice Description Implementation
Minimal Base Images Use slim/alpine variants FROM alpine:3.17
Non-root User Run as non-privileged user USER appuser
Secret Management Use build arguments ARG SECRET

$1

1. Dependency Caching

`dockerfile

# Good practice

COPY package*.json ./

RUN npm ci

COPY . .

# Bad practice

COPY . .

RUN npm ci

`

2. Build Context Optimization

`dockerfile

# .dockerignore

node_modules

npm-debug.log

Dockerfile

.dockerignore

.git

.gitignore

README.md

``

$1

$1

Issue Cause Solution
Build Failures Missing dependencies Check build stage
Large Images Unnecessary files Use .dockerignore
Cache Issues Layer ordering Optimize COPY

$1

1. [Docker Multi-stage Builds](https://docs.docker.com/build/building/multi-stage/)

2. [Docker Build Performance](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/)

3. [Container Security](https://snyk.io/learn/container-security/)

4. [Docker Layer Caching](https://docs.docker.com/build/cache/)

5. [Docker BuildKit](https://docs.docker.com/build/buildkit/)

6. [Container Optimization](https://docs.docker.com/develop/develop-images/guidelines/)

  • [Docker Security Scanning](/posts/docker/security-scanning) - Container security
  • [Docker Compose V2](/posts/docker/compose-v2) - Container orchestration
  • [Docker Desktop Alternatives](/posts/docker/desktop-alternatives) - Development environment
  • [Docker Kubernetes Integration](/posts/docker/kubernetes-integration) - Container orchestration
  • Why This Matters

    Understanding the business and technical context helps you make informed decisions rather than blindly following patterns.

    Trade-offs to Consider

    Every architectural decision involves trade-offs. Consider your specific requirements, team expertise, and scale when evaluating options.

    When NOT to Use This

    Knowing when a solution doesn't apply is as valuable as knowing when it does. Consider alternatives for your specific situation.

    Decision Framework

    Use this framework to evaluate whether this approach is right for your use case based on your specific constraints and requirements.