Back openDesk Edu for a sovereign, open-source education — every vote counts.
Vote nowSave products you love by clicking the heart icon.
Learn to tune sysctls, ulimits, cgroups, and Kubernetes resource controls for optimal performance, stability, and security in production workloads.
Multi-stage builds are the single most impactful optimization for Docker images. They separate build dependencies from runtime, producing minimal, secure production images.
A typical Node.js Dockerfile:
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
CMD ["npm", "start"]
```text
Result: **1.2 GB image** containing:
- Full Node.js toolchain
- npm cache
- devDependencies
- TypeScript compiler
- Source maps
- Build artifacts
None of this is needed at runtime.
## Multi-Stage Solution
```dockerfile
# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production=false
COPY . .
RUN npm run build
# Stage 2: Production
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
USER node
CMD ["node", "dist/index.js"]
```text
Result: **180 MB image** - 85% smaller.
## Pattern Library
### Go Application
```dockerfile
# Build stage
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o main .
# Runtime stage
FROM scratch
COPY --from=builder /app/main /main
ENTRYPOINT ["/main"]
```text
Result: **~10 MB image**
### React Application
```dockerfile
# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Runtime stage (nginx)
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```text
### Python with Virtual Environment
```dockerfile
# Build stage
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
# Runtime stage
FROM python:3.11-slim
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY . .
CMD ["python", "app.py"]
```text
## Security Benefits
### Fewer Vulnerabilities
```bash
# Large image
docker scout quickview myapp:v1
# 47 CVEs detected
# Multi-stage image
docker scout quickview myapp:v2
# 3 CVEs detected
```text
### No Build Tools
Attackers can't compile exploits if there's no compiler:
```dockerfile
# Runtime has no gcc, make, or build tools
FROM alpine:3.18
# Only what's needed to run
```text
## Best Practices
### Use Specific Base Images
```dockerfile
# Bad: floating tag
FROM node:18
# Good: pinned digest
FROM node:18.19.0-alpine3.19@sha256:abc123...
```text
### Minimize Layers
```dockerfile
# Bad: multiple layers
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean
# Good: single layer
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*
```text
### Use .dockerignore
```text
node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
README.md
.env
coverage
.nyc_output
```text
### Non-Root User
```dockerfile
FROM node:18-alpine
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
USER appuser
```text
## Size Comparison
| Approach | Size | Security |
|---|---|---|
| Single stage | 1.2 GB | 47 CVEs |
| Multi-stage | 180 MB | 3 CVEs |
| Multi-stage + distroless | 80 MB | 0 CVEs |
## Conclusion
Multi-stage builds should be the default for all production Dockerfiles. They reduce size, improve security, and enforce clean separation between build and runtime environments.