Dockerfile Best Practices: Multi-Stage Builds, Security, and Optimization
Learn how to write production-ready Dockerfiles — multi-stage builds, layer caching, security hardening, image size optimization, and templates for every language.
DevToolsHub Team20 min read536 words
Why Dockerfile Quality Matters
A poorly written Dockerfile leads to:
- Huge images — 1GB+ images that take minutes to pull
- Security vulnerabilities — Running as root, including unnecessary packages
- Slow builds — Rebuilding everything on every code change
- Unreliable deployments — Missing health checks, no graceful shutdown
The Basics
# Always use specific version tags
FROM node:20-alpine
# Set working directory
WORKDIR /app
# Copy dependency files first (layer caching!)
COPY package.json package-lock.json ./
RUN npm ci --only=production
# Copy application code
COPY . .
# Use non-root user
USER node
# Document the port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
# Start command
CMD ["node", "server.js"]
Multi-Stage Builds
Multi-stage builds are the single most impactful optimization. They separate the build environment from the runtime environment:
# Stage 1: Build (includes dev dependencies, compilers, etc.)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Production (only runtime dependencies)
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]
Result: Build stage might be 500MB+, but the final image is only 100-150MB.
Layer Caching
Docker caches each layer. If a layer hasn't changed, Docker reuses the cached version. Order your Dockerfile from least-frequently-changed to most-frequently-changed:
# ✅ Good order (dependencies change less often than code)
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
# ❌ Bad order (code changes invalidate npm install cache)
COPY . .
RUN npm ci
Security Best Practices
1. Never Run as Root
# Create a non-root user
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
# Switch to non-root user
USER appuser
2. Use Minimal Base Images
# ❌ Full image (900MB+)
FROM node:20
# ✅ Alpine (150MB)
FROM node:20-alpine
# ✅ Distroless (even smaller, no shell)
FROM gcr.io/distroless/nodejs20-debian12
3. Don't Include Secrets
# ❌ Never do this
ENV API_KEY=sk_live_abc123
# ✅ Pass at runtime
# docker run -e API_KEY=sk_live_abc123 myapp
4. Scan for Vulnerabilities
# Scan with Docker Scout
docker scout cves myimage:latest
# Scan with Trivy
trivy image myimage:latest
Image Size Optimization
| Technique | Savings |
|---|---|
| Alpine base image | 60-80% |
| Multi-stage build | 50-70% |
| .dockerignore | 10-30% |
| Combine RUN commands | 5-15% |
| Remove package caches | 5-10% |
.dockerignore
node_modules
.git
.gitignore
README.md
docker-compose*.yml
.env*
.next
coverage
tests
Generate optimized Dockerfiles with our Dockerfile Generator — select your language and get a production-ready Dockerfile with multi-stage builds and security best practices.
Related Tools
- Dockerfile Generator — Generate optimized Dockerfiles
- Docker Compose Generator — Build docker-compose.yml
- .gitignore Generator — Generate .gitignore files
- YAML Formatter — Format Docker Compose YAML
- Docker Commands Cheat Sheet — Docker reference
- Linux Commands Cheat Sheet — Linux reference
- Chmod Calculator — Set file permissions
dockerfiledockermulti-stage builddocker best practicesdockerfile optimizationdocker securitycontainer