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