Bonnes pratiques Docker
Objectifs du chapitre
- Optimiser la taille des images
- Améliorer la sécurité des conteneurs
- Suivre les conventions de l'industrie
- Préparer vos images pour la production
1 - Optimisation des images
Choisir la bonne image de base
# ❌ 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
Comparaison des tailles
| Image | Taille | Usage |
|---|---|---|
ubuntu:22.04 | ~77 Mo | Développement |
node:18 | ~900 Mo | Dev avec outils complets |
node:18-slim | ~200 Mo | Production légère |
node:18-alpine | ~50 Mo | Production optimisée |
distroless | ~20 Mo | Production sécurisée |
Minimiser les couches
# ❌ 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/*
Nettoyer dans la même couche
# ❌ 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 - Optimisation du cache
Ordre des 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"]
Fichier .dockerignore
# 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 - Sécurité
Ne pas exécuter en 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"]
Utiliser des images signées
# Activer le content trust
export DOCKER_CONTENT_TRUST=1
# Les images doivent être signées
docker pull nginx:latest
Scanner les vulnérabilités
# 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 et données sensibles
# ❌ 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
Image en lecture seule
# Exécuter en lecture seule
docker run --read-only \
--tmpfs /tmp \
--tmpfs /var/run \
myapp
4 - Logging et monitoring
Configurer les 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
Configuration globale des logs
// /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
Exemple Node.js
# 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"]
Exemple Go
# 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"]
Exemple Python
# 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 - Gestion des ressources
Limites CPU et mémoire
# 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
Politique de redémarrage
# 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 et versionning
Convention de nommage
# 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
Tags sémantiques
# 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 - Checklist production
Avant le déploiement
## 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
Labels OCI
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 - Dockerfile complet optimisé
Application Node.js production-ready
# 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"]
Résumé
| Catégorie | Bonne pratique |
|---|---|
| Images | Utiliser des images légères (alpine, slim) |
| Couches | Minimiser et optimiser l'ordre |
| Cache | Copier les dépendances avant le code |
| Sécurité | Ne pas exécuter en root |
| Secrets | Ne jamais hardcoder, utiliser Docker secrets |
| Logs | Rediriger vers stdout/stderr |
| Health | Toujours définir un HEALTHCHECK |
| Ressources | Définir des limites CPU/mémoire |
| Tags | Utiliser le versioning sémantique |
Points clés
- Multi-stage builds pour des images légères et sécurisées
- Toujours utiliser un utilisateur non-root
- Scanner régulièrement les vulnérabilités
- Documenter et versionner vos images
Exercices pratiques
- Optimisez un Dockerfile existant avec un multi-stage build
- Ajoutez un utilisateur non-root et un healthcheck
- Réduisez la taille d'une image de 50%+
- Configurez les limites de ressources dans Docker Compose