HxHippy

Multi-stage Builds

Use multi-stage builds for smaller, optimized production images.

Last updated: 2025-01-15

Multi-stage Builds

Build and compile in one stage, copy only necessary artifacts to the final image.

Basic Pattern

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

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

Go Application

Go compiles to a single binary, perfect for minimal images.

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

# Production stage - scratch (no OS)
FROM scratch
COPY --from=builder /app/main /main
ENTRYPOINT ["/main"]

Node.js with TypeScript

# Build stage
FROM node:20-alpine AS builder
WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY tsconfig.json ./
COPY src ./src
RUN npm run build
RUN npm prune --production

# Production stage
FROM node:20-alpine
WORKDIR /app

# Copy only production dependencies and built code
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package.json ./

USER node
EXPOSE 3000
CMD ["node", "dist/index.js"]

Python Application

# Build stage - install and compile
FROM python:3.12-slim AS builder
WORKDIR /app

RUN pip install --upgrade pip
COPY requirements.txt .
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt

# Production stage
FROM python:3.12-slim
WORKDIR /app

# Copy wheels and install
COPY --from=builder /wheels /wheels
RUN pip install --no-cache /wheels/*

COPY . .

USER nobody
EXPOSE 8000
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "app:app"]

React Frontend

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

# Production stage - Nginx
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Named Stages

# Base stage with common dependencies
FROM node:20-alpine AS base
WORKDIR /app
COPY package*.json ./

# Development stage
FROM base AS development
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]

# Test stage
FROM base AS test
RUN npm ci
COPY . .
CMD ["npm", "test"]

# Build stage
FROM base AS builder
RUN npm ci
COPY . .
RUN npm run build

# Production stage
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]
# Build specific stage
docker build --target development -t myapp:dev .
docker build --target test -t myapp:test .
docker build --target production -t myapp:prod .

Size Comparison

Stage Base Image Typical Size
Builder node:20 1.1 GB
Production node:20-alpine 150 MB
Go + scratch scratch 10-20 MB
Distroless gcr.io/distroless/static 2-5 MB

Benefits

  1. Smaller images - Only runtime dependencies
  2. Better security - No build tools in production
  3. Faster deployment - Smaller images transfer faster
  4. Cleaner separation - Build vs runtime concerns
intermediate Dockerfile Updated 2025-01-15
  • dockerfile
  • multi-stage
  • build
  • optimization
  • production