Skillversitas

Docker Multi-Stage Builds

Multi-stage builds allow you to create smaller images by separating build and runtime.

Basic Concept

# Stage 1: Build
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Production
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html

Benefits

  1. Smaller image: Only copy what is needed
  2. Faster builds: More effective layer caching
  3. Security: Build tools are not included in production

Multi-Stage Examples

Node.js App

# Build stage
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production stage
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]

Go App

# Build stage
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server

# Production stage
FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ["/server"]

Java App

# Build stage
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

# Production stage
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]

Use Case: Application

# Frontend
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf

# Backend
FROM node:18 AS backend-builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM node:18-alpine
WORKDIR /app
COPY --from=backend-builder /app/dist ./dist
COPY --from=backend-builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]

Everyday Use Case

Case: A developer needs to reduce the image size.

# Build multi-stage
docker build -t myapp:latest .

# Compare sizes
docker images myapp

# Single stage: ~900MB
# Multi-stage: ~150MB

Named Stages

# Named stages for clarity
FROM node:18 AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM node:18 AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:18-alpine AS production
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=deps /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]

Best Practices

  1. Use named stages: For documentation
  2. Copy selectively: Only copy what is needed
  3. Use .dockerignore: To reduce the context
  4. Cache dependencies: Copy package*.json first
  5. Use slim images: For the production stage

Conclusion

Multi-stage builds are very important for optimizing Docker images. Use them for all production applications.