Docker Best Practices

Multi-Stage Builds

Always use multi-stage builds to keep images small:

# Stage 1: Build
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# Stage 2: Runtime
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY . .
USER appuser
CMD ["gunicorn", "-b", "0.0.0.0:5000", "app:app"]

Security

Run as Non-Root

RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser
USER appuser

Health Checks

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:5000/health || exit 1

Minimal Base Images

Stack Recommended Base
Python python:3.11-slim
PHP php:8.3-fpm
Ruby ruby:3.2-slim
Node node:20-slim

docker-compose.yml Pattern

services:
  app:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/app  # Development hot-reload
    environment:
      - FLASK_DEBUG=1
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
      interval: 30s
      timeout: 3s
      retries: 3

Image Tagging Strategy

Tag When
branch-name Every commit on a branch
latest Merges to default branch
v1.2.3 Git tags (releases)

Best Practices

  • Always include a .dockerignore file
  • Pin base image versions (use python:3.11-slim, not python:latest)
  • Use --no-cache-dir for pip to reduce image size
  • Copy requirements.txt separately before code for better layer caching
  • Set WORKDIR explicitly
  • Use EXPOSE to document ports
  • Never store secrets in images; use environment variables