Aller au contenu principal

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

ImageTailleUsage
ubuntu:22.04~77 MoDéveloppement
node:18~900 MoDev avec outils complets
node:18-slim~200 MoProduction légère
node:18-alpine~50 MoProduction optimisée
distroless~20 MoProduction 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égorieBonne pratique
ImagesUtiliser des images légères (alpine, slim)
CouchesMinimiser et optimiser l'ordre
CacheCopier les dépendances avant le code
SécuritéNe pas exécuter en root
SecretsNe jamais hardcoder, utiliser Docker secrets
LogsRediriger vers stdout/stderr
HealthToujours définir un HEALTHCHECK
RessourcesDéfinir des limites CPU/mémoire
TagsUtiliser 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

  1. Optimisez un Dockerfile existant avec un multi-stage build
  2. Ajoutez un utilisateur non-root et un healthcheck
  3. Réduisez la taille d'une image de 50%+
  4. Configurez les limites de ressources dans Docker Compose

← Docker Compose | Exercices et projets →