Dockerfile Best Practices
Build faster, smaller, and more secure Docker images in 2025. Learn the patterns that top engineering teams use to optimize their containerized applications.
Why Dockerfile Optimization Matters
In 2025, container image size and build speed directly impact your deployment velocity, CI/CD costs, and application security. A well-optimized Dockerfile can reduce your image size by 10x, cut build times in half, and significantly reduce your attack surface.
Faster Deployments
Smaller images = faster pulls and deploys
Lower Costs
Reduced storage and bandwidth costs
Better Security
Fewer packages = smaller attack surface
1. Choose Minimal Base Images
Alpine vs Debian vs Distroless
Your base image choice has the biggest impact on final image size and security. In 2025, we have better options than ever.
❌ Avoid
# Using full Ubuntu base (800MB+)
FROM ubuntu:latest
RUN apt-get update && apt-get install -y \\
python3 \\
python3-pip \\
curl \\
vim \\
git
# Image size: ~1GBProblems: Huge size, many unnecessary packages, security vulnerabilities
✅ Better
# Using Alpine (5MB base)
FROM python:3.11-alpine
RUN apk add --no-cache \\
gcc \\
musl-dev
# Image size: ~50MB
# Or even better: Use distroless for productionBenefits: 20x smaller, fewer vulnerabilities, faster deploys
Image Size Comparison 2025
- • python:3.11 (Debian-based): ~920MB
- • python:3.11-slim: ~130MB
- • python:3.11-alpine: ~50MB
- • gcr.io/distroless/python3: ~60MB (most secure)
2. Master Multi-Stage Builds
Separate Build and Runtime Dependencies
Multi-stage builds let you use heavy build tools without including them in your final image. This is the #1 technique for reducing image size in 2025.
❌ Single-Stage Build
FROM node:18
WORKDIR /app
# Install ALL dependencies (dev + prod)
COPY package*.json ./
RUN npm install
# Build application
COPY . .
RUN npm run build
# Final image includes node_modules, build tools, source code
# Image size: ~1.2GB
CMD ["npm", "start"]✅ Multi-Stage Build
# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# Build the application
COPY . .
RUN npm run build
# Stage 2: Production
FROM node:18-alpine
WORKDIR /app
# Copy only production dependencies and built files
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package*.json ./
# Image size: ~120MB (10x smaller!)
CMD ["node", "dist/index.js"]Why This Works
The final image only includes the runtime dependencies and compiled code. All the build tools (TypeScript compiler, webpack, test frameworks) are left behind in the builder stage.
3. Optimize Layer Caching
Order Matters: From Least to Most Frequently Changed
Docker caches each layer. When a layer changes, all subsequent layers must be rebuilt. Order your instructions to maximize cache hits.
❌ Poor Caching
FROM python:3.11-slim
WORKDIR /app
# Copy everything first (cache invalidated on ANY file change)
COPY . .
# Dependencies reinstalled on EVERY build
RUN pip install -r requirements.txt
CMD ["python", "app.py"]Problem: Changing app.py invalidates the pip install cache
✅ Optimized Caching
FROM python:3.11-slim
WORKDIR /app
# Copy only dependency files first
COPY requirements.txt .
# Install dependencies (cached unless requirements.txt changes)
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code last
COPY . .
CMD ["python", "app.py"]Benefit: Dependencies cached across app code changes
Layer Order Best Practice
- Base image and system packages
- Application dependencies (package.json, requirements.txt)
- Application source code
- Configuration files (if they change frequently)
4. Security Best Practices
Run as Non-Root User
Running containers as root is a major security risk in 2025. Always create and use a non-root user.
❌ Security Risk
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install
# Running as root!
CMD ["node", "server.js"]✅ Secure
FROM node:18-alpine
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \\
adduser -S nodejs -u 1001
WORKDIR /app
COPY --chown=nodejs:nodejs . .
RUN npm ci --only=production
# Switch to non-root user
USER nodejs
CMD ["node", "server.js"]Other Security Best Practices
Use specific image tags, not "latest"
# Bad: FROM node:latest
# Good: FROM node:18.17.0-alpineScan images for vulnerabilities
# Add to your CI/CD pipeline
docker scan my-image:latest
# Or use Trivy
trivy image my-image:latestDon't include secrets in the image
# Bad: COPY .env .
# Good: Use environment variables or secrets management
# docker run -e API_KEY=$API_KEY my-imageUse .dockerignore to exclude sensitive files
# .dockerignore
.git
node_modules
.env
.env.local
*.log
.DS_StoreComplete Production-Ready Example
Node.js Application (2025 Best Practices)
# syntax=docker/dockerfile:1.4
# Use BuildKit features
# Stage 1: Dependencies
FROM node:18-alpine AS deps
WORKDIR /app
# Copy package files
COPY package.json package-lock.json ./
# Install dependencies with cache mount
RUN --mount=type=cache,target=/root/.npm \\
npm ci --only=production
# Stage 2: Builder
FROM node:18-alpine AS builder
WORKDIR /app
# Copy dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build the application
RUN npm run build
# Stage 3: Production
FROM node:18-alpine AS runner
WORKDIR /app
# Create non-root user
RUN addgroup --system --gid 1001 nodejs && \\
adduser --system --uid 1001 nodejs
# Copy only necessary files
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/package.json ./
# Security: Run as non-root
USER nodejs
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
CMD node healthcheck.js
# Start application
CMD ["node", "dist/index.js"]
# Final image size: ~120MB
# Build time with cache: ~10 seconds
# Security: Non-root user, minimal attack surface✅ Multi-stage
Minimal final image
✅ Optimized caching
Fast rebuilds
✅ Secure
Non-root user
Python FastAPI Application
# Stage 1: Builder
FROM python:3.11-slim AS builder
WORKDIR /app
# Install build dependencies
RUN apt-get update && apt-get install -y \\
gcc \\
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Stage 2: Production
FROM python:3.11-slim
WORKDIR /app
# Create non-root user
RUN useradd -m -u 1000 appuser
# Copy Python packages from builder
COPY --from=builder /root/.local /home/appuser/.local
# Copy application code
COPY --chown=appuser:appuser . .
# Update PATH
ENV PATH=/home/appuser/.local/bin:$PATH
# Switch to non-root user
USER appuser
EXPOSE 8000
# Health check
HEALTHCHECK CMD curl --fail http://localhost:8000/health || exit 1
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# Final image size: ~180MBQuick Wins Checklist for 2025
- Use Alpine or slim base images
- Implement multi-stage builds
- Copy package files before source code
- Run as non-root user
- Use specific image tags
- Add .dockerignore file
- Use --no-cache-dir for pip/npm
- Scan images for vulnerabilities
- Add health checks
- Clean up in the same layer