Why Your Docker Images Are Too Big
The average Docker image in production is 500MB-1GB. With proper optimization, most can be reduced to 50-100MB. Hereβs how.
1. Multi-Stage Builds
# Build stage
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Production stage
FROM node:22-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "dist/index.js"]
Result: Build dependencies never make it to the final image.
2. Use Distroless or Alpine
| Base Image | Size |
|---|---|
node:22 |
1.1 GB |
node:22-slim |
220 MB |
node:22-alpine |
130 MB |
gcr.io/distroless/nodejs22 |
50 MB |
3. Security Best Practices
- Never run as root: Always add
USER nodeorUSER 1001 - Pin versions: Use
node:22.5.0-alpinenotnode:latest - Scan images:
docker scout cves myimage:latest - Use .dockerignore: Exclude
.git,node_modules,.env - No secrets in images: Use runtime environment variables or secrets managers
4. Layer Caching Optimization
Order your Dockerfile from least-changed to most-changed:
FROM node:22-alpine
WORKDIR /app
# 1. System deps (rarely change)
RUN apk add --no-cache curl
# 2. Package files (change occasionally)
COPY package*.json ./
RUN npm ci
# 3. Source code (changes frequently)
COPY . .
RUN npm run build
5. Health Checks
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
This lets orchestrators (K8s, ECS, Docker Swarm) know when your container is actually ready.