Docker best practices
Chapter objectives
- Optimize image size
- Improve container security
- Follow industry conventions
- Prepare your images for production
1 - Image optimization
Choose the right base image
# ❌ Image complète (1 Go+)
FROM ubuntu:22.04
# ⚠️ Image standard (~900 Mo)
FROM node:18
# ✅ Image slim (~200 Mo)
FROM node:18-slim
# ✅ Image Alpine (~50 Mo)
FROM node:18-alpine
# ✅ Image distroless (~20 Mo)
FROM gcr.io/distroless/nodejs18-debian11
# ✅ Image scratch (0 Mo, binaire statique)
FROM scratch
Size comparison
| Image | Size | Usage |
|---|---|---|
ubuntu:22.04 | ~77 MB | Development |
node:18 | ~900 MB | Dev with full tooling |
node:18-slim | ~200 MB | Lightweight production |
node:18-alpine | ~50 MB | Optimized production |
distroless | ~20 MB | Secure production |
Minimize layers
# ❌ Plusieurs couches
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
RUN apt-get clean
# ✅ Une seule couche optimisée
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl \
git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Clean up in the same layer
# ❌ Mauvais - le cache reste dans la couche précédente
RUN apt-get update && apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# ✅ Bon - nettoyage dans la même couche
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*
2 - Cache optimization
Order of instructions
# ❌ Mauvais - le cache est invalidé à chaque changement de code
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "index.js"]
# ✅ Bon - les dépendances sont en cache
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
CMD ["node", "index.js"]
The .dockerignore file
# Dépendances
node_modules
vendor
__pycache__
# Git
.git
.gitignore
# IDE
.idea
.vscode
*.swp
# Logs et données
*.log
logs
data
# Tests
coverage
.nyc_output
test
tests
__tests__
# Docker
Dockerfile*
docker-compose*
.docker
# Documentation
README.md
docs
# Environnement
.env
.env.*
*.local
3 - Security
Do not run as root
# Créer un utilisateur non-root
FROM node:18-alpine
# Créer le groupe et l'utilisateur
RUN addgroup -g 1001 -S nodejs && \
adduser -S -u 1001 -G nodejs nodejs
WORKDIR /app
# Changer la propriété des fichiers
COPY --chown=nodejs:nodejs . .
# Utiliser l'utilisateur non-root
USER nodejs
CMD ["node", "index.js"]
Use signed images
# Activer le content trust
export DOCKER_CONTENT_TRUST=1
# Les images doivent être signées
docker pull nginx:latest
Scan for vulnerabilities
# Avec Docker Scout
docker scout quickview nginx:latest
docker scout cves nginx:latest
# Avec Trivy
trivy image nginx:latest
# Avec Snyk
snyk container test nginx:latest
Secrets and sensitive data
# ❌ Ne JAMAIS faire ça
ENV API_KEY=secret123
COPY credentials.json /app/
# ✅ Utiliser des secrets Docker
# docker run --secret id=api_key,src=./api_key.txt myapp
# Docker Compose avec secrets
services:
app:
image: myapp
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
Read-only image
# Exécuter en lecture seule
docker run --read-only \
--tmpfs /tmp \
--tmpfs /var/run \
myapp
4 - Logging and monitoring
Configure logs
# Rediriger les logs vers stdout/stderr
CMD ["nginx", "-g", "daemon off;"]
# Limiter la taille des logs
docker run -d \
--log-driver json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
myapp
Global log configuration
// /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Healthchecks
FROM nginx:alpine
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1
# Docker Compose
services:
web:
image: nginx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
5 - Multi-stage builds
Node.js example
# Stage 1: Build
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Production
FROM node:18-alpine AS production
WORKDIR /app
# Créer un utilisateur non-root
RUN addgroup -g 1001 -S nodejs && \
adduser -S -u 1001 -G nodejs nodejs
# Copier uniquement le nécessaire
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 ./
USER nodejs
EXPOSE 3000
CMD ["node", "dist/index.js"]
Go example
# Build
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o main .
# Production - Image scratch minimale
FROM scratch
COPY --from=builder /app/main /main
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
USER 1000:1000
ENTRYPOINT ["/main"]
Python example
# Build
FROM python:3.11 AS builder
WORKDIR /app
RUN pip install --user pipenv
COPY Pipfile* ./
RUN pipenv requirements > requirements.txt
RUN pip install --user -r requirements.txt
# Production
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser
CMD ["python", "app.py"]
6 - Resource management
CPU and memory limits
# Ligne de commande
docker run -d \
--memory=512m \
--memory-swap=512m \
--cpus=0.5 \
myapp
# Docker Compose
services:
app:
image: myapp
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
Restart policy
# Options de restart
docker run -d --restart=no myapp # Par défaut
docker run -d --restart=on-failure myapp # Seulement en cas d'erreur
docker run -d --restart=always myapp # Toujours
docker run -d --restart=unless-stopped myapp # Sauf si arrêté manuellement
# Docker Compose
services:
app:
image: myapp
restart: unless-stopped
7 - Tags and versioning
Naming convention
# Structure recommandée
registre/organisation/image:version
# Exemples
ghcr.io/myorg/myapp:1.2.3
docker.io/myuser/myapp:latest
registry.example.com/myapp:v1.0.0-alpine
Semantic tags
# Tagger avec version sémantique
docker tag myapp:latest myapp:1.0.0
docker tag myapp:latest myapp:1.0
docker tag myapp:latest myapp:1
# Un utilisateur peut choisir le niveau de spécificité
docker pull myapp:1.0.0 # Version exacte
docker pull myapp:1.0 # Dernière patch de 1.0
docker pull myapp:1 # Dernière minor de 1.x
CI/CD tags
# Tags pour CI/CD
myapp:main # Branche principale
myapp:develop # Branche de développement
myapp:pr-123 # Pull request
myapp:sha-abc123 # Commit SHA
myapp:1.0.0-rc.1 # Release candidate
8 - Production checklist
Before deployment
## Checklist Image Docker
### Sécurité
- [ ] Image de base officielle ou vérifiée
- [ ] Version de base épinglée (pas :latest)
- [ ] Exécution en tant qu'utilisateur non-root
- [ ] Pas de secrets hardcodés
- [ ] Scan de vulnérabilités effectué
- [ ] Permissions minimales
### Optimisation
- [ ] Multi-stage build utilisé
- [ ] .dockerignore configuré
- [ ] Couches ordonnées pour le cache
- [ ] Nettoyage dans les couches RUN
- [ ] Image de base légère (alpine, slim, distroless)
### Opérations
- [ ] HEALTHCHECK configuré
- [ ] Logs vers stdout/stderr
- [ ] Limites de ressources définies
- [ ] Politique de restart configurée
- [ ] Labels/metadata ajoutés
### Documentation
- [ ] README avec instructions de build
- [ ] Variables d'environnement documentées
- [ ] Ports exposés documentés
OCI labels
LABEL org.opencontainers.image.source="https://github.com/user/repo"
LABEL org.opencontainers.image.description="Description de l'application"
LABEL org.opencontainers.image.licenses="MIT"
LABEL org.opencontainers.image.version="1.0.0"
LABEL org.opencontainers.image.vendor="MyCompany"
LABEL org.opencontainers.image.authors="[email protected]"
9 - Complete optimized Dockerfile
Production-ready Node.js application
# syntax=docker/dockerfile:1
###################
# BUILD STAGE
###################
FROM node:18-alpine AS builder
# Installer les dépendances de build
RUN apk add --no-cache python3 make g++
WORKDIR /app
# Copier les fichiers de dépendances
COPY package*.json ./
# Installer les dépendances
RUN npm ci --only=production && \
npm cache clean --force
# Copier le code source
COPY . .
# Build l'application
RUN npm run build
###################
# PRODUCTION STAGE
###################
FROM node:18-alpine AS production
# Labels OCI
LABEL org.opencontainers.image.source="https://github.com/example/myapp"
LABEL org.opencontainers.image.description="My Production App"
LABEL org.opencontainers.image.version="1.0.0"
# Variables d'environnement
ENV NODE_ENV=production \
PORT=3000
# Créer un utilisateur non-root
RUN addgroup -g 1001 -S nodejs && \
adduser -S -u 1001 -G nodejs nodejs
WORKDIR /app
# Copier depuis le builder
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 ./
# Utiliser l'utilisateur non-root
USER nodejs
# Exposer le port
EXPOSE 3000
# Healthcheck
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
# Commande de démarrage
CMD ["node", "dist/index.js"]
Summary
| Category | Best practice |
|---|---|
| Images | Use lightweight images (alpine, slim) |
| Layers | Minimize and optimize the order |
| Cache | Copy dependencies before the code |
| Security | Do not run as root |
| Secrets | Never hardcode, use Docker secrets |
| Logs | Redirect to stdout/stderr |
| Health | Always define a HEALTHCHECK |
| Resources | Define CPU/memory limits |
| Tags | Use semantic versioning |
Key points
- Multi-stage builds for lightweight and secure images
- Always use a non-root user
- Regularly scan for vulnerabilities
- Document and version your images
Hands-on exercises
- Optimize an existing Dockerfile with a multi-stage build
- Add a non-root user and a healthcheck
- Reduce an image's size by 50%+
- Configure resource limits in Docker Compose