Docker Multi-stage Builds: Optimizing Container Images
Docker

Docker Multi-stage Builds: Optimizing Container Images

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

March 15, 2024
DevHub Team
5 min read

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.

Understanding Multi-stage Builds

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

Basic Multi-stage Pattern

Node.js Application Example

# 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"]

Go Application Example

# 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"]

Advanced Patterns

Multi-stage with Testing

# 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"]

Multi-stage with Multiple Artifacts

# 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"]

Optimization Techniques

Layer Optimization

TechniqueDescriptionImpact
Cache DependenciesCopy dependency files firstFaster builds
Minimize LayersCombine RUN commandsSmaller images
Clean UpRemove build artifactsReduced size

Build Arguments

# 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"]

Language-specific Patterns

Python Application

# 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"]

Java Spring Boot Application

# 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"]

CI/CD Integration

GitHub Actions Example

# .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

Best Practices

Security Considerations

PracticeDescriptionImplementation
Minimal Base ImagesUse slim/alpine variantsFROM alpine:3.17
Non-root UserRun as non-privileged userUSER appuser
Secret ManagementUse build argumentsARG SECRET

Performance Tips

  1. Dependency Caching

    # Good practice COPY package*.json ./ RUN npm ci COPY . . # Bad practice COPY . . RUN npm ci
  2. Build Context Optimization

    # .dockerignore node_modules npm-debug.log Dockerfile .dockerignore .git .gitignore README.md

Troubleshooting Guide

Common Issues

IssueCauseSolution
Build FailuresMissing dependenciesCheck build stage
Large ImagesUnnecessary filesUse .dockerignore
Cache IssuesLayer orderingOptimize COPY

References

  1. Docker Multi-stage Builds
  2. Docker Build Performance
  3. Container Security
  4. Docker Layer Caching
  5. Docker BuildKit
  6. Container Optimization

Related Posts

Docker
Containers
DevOps
Optimization