DevOps
Docker Best Practices for 2024
Essential Docker tips and best practices for building secure, efficient, and maintainable container images.
Docker has become essential for modern software development. Let's explore the best practices for building production-ready container images in 2024.
Use Multi-Stage Builds#
Multi-stage builds help create smaller, more secure images:
# 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
USER node
EXPOSE 3000
CMD ["node", "dist/main.js"]Why Multi-Stage?
Multi-stage builds can reduce your image size by 90% or more by excluding build tools and dev dependencies from the final image.
Choose the Right Base Image#
Your base image choice significantly impacts security and size:
| Image | Size | Use Case |
|---|---|---|
node:20 | ~1GB | Development only |
node:20-slim | ~200MB | Most applications |
node:20-alpine | ~130MB | Size-critical apps |
distroless/nodejs | ~100MB | Maximum security |
# Prefer slim or alpine variants
FROM python:3.12-slim
# Or use distroless for maximum security
FROM gcr.io/distroless/python3Order Layers for Caching#
Order your Dockerfile commands from least to most frequently changing:
FROM python:3.12-slim
# System dependencies (rarely change)
RUN apt-get update && apt-get install -y \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Python dependencies (change occasionally)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Application code (changes frequently)
COPY . .
CMD ["python", "app.py"]Docker caches each layer. When a layer changes, all subsequent layers must be rebuilt. Ordering matters!
Security Best Practices#
Don't Run as Root#
# Create a non-root user
RUN addgroup --system app && adduser --system --group app
# Switch to non-root user
USER appUse .dockerignore#
Create a .dockerignore file to exclude unnecessary files:
# .dockerignore
node_modules
.git
.env
*.log
Dockerfile
docker-compose*.yml
.dockerignore
README.md
tests/Scan for Vulnerabilities#
# Using Docker Scout
docker scout cves myimage:latest
# Using Trivy
trivy image myimage:latestSecurity Alert
Always scan your images for vulnerabilities before deploying to production. Automate this in your CI/CD pipeline!
Optimize for Production#
Use Specific Tags#
# Bad - unpredictable
FROM node:latest
# Good - reproducible
FROM node:20.10.0-alpine3.19Health Checks#
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1Resource Limits#
# docker-compose.yml
services:
app:
image: myapp:latest
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256MDocker Compose Best Practices#
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
target: production
environment:
- NODE_ENV=production
env_file:
- .env
restart: unless-stopped
networks:
- app-network
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
networks:
app-network:
driver: bridgeCI/CD Integration#
Example GitHub Actions workflow:
name: Build and Push
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Scan for vulnerabilities
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
- name: Push to registry
run: |
docker tag myapp:${{ github.sha }} registry/myapp:latest
docker push registry/myapp:latestConclusion#
Following these Docker best practices will help you build smaller, more secure, and more maintainable container images. Remember:
- Use multi-stage builds to minimize image size
- Choose appropriate base images for your use case
- Order layers strategically for better caching
- Never run as root in production
- Scan images regularly for vulnerabilities
- Use specific tags for reproducibility
Happy containerizing! 🐳
Related posts
Secure CI/CD from GitHub Actions to a VPS: Docker, GHCR, nginx, and the Traps
A production field guide: GitHub Actions builds a Django API image, pushes it to GHCR, and deploys it over SSH onto a VPS that already hosts other sites. Secrets, Compose interpolation, TLS redirect loops, and the mistakes that take a weekend.
Sep 30, 2026 · 14 min read