1. Multi-stage Builds

Un build multi-stage utilise plusieurs FROM dans un même Dockerfile. Seul le dernier stage est gardé — les outils de build (compilateurs, node_modules de dev) n'arrivent jamais en production.

# Dockerfile multi-stage Node.js

# Stage 1 : Build
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci                   # installe TOUT (dev deps inclus)
COPY . .
RUN npm run build            # compile le frontend, transpile TS...

# Stage 2 : Production (image finale)
FROM node:18-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production  # seulement les deps de prod
COPY --from=builder /app/dist ./dist  # copier le build
USER node
CMD ["node", "dist/server.js"]

# Résultat : image finale sans les 800 MB de node_modules de dev !

2. Choisir des images légères

Base imageTailleShellPackage manager
node:18~950 MBbashapt (Debian)
node:18-slim~240 MBbashapt (minimal)
node:18-alpine~115 MBshapk
gcr.io/distroless/nodejs18~85 MBAucunAucun
# Alpine — très légère, basée sur musl libc
FROM python:3.11-alpine
RUN apk add --no-cache gcc musl-dev  # apk au lieu de apt

# Distroless — pas de shell, surface d'attaque minimale
FROM gcr.io/distroless/java17
COPY --from=builder /app/target/app.jar /app.jar
CMD ["/app.jar"]

3. Optimiser l'utilisation du cache

# Règle : du moins au plus souvent modifié

# ❌ MAUVAIS — COPY . invalide tout à chaque changement
FROM node:18-alpine
WORKDIR /app
COPY . .                      # invalide le cache npm
RUN npm install              # réinstalle tout à chaque build

# ✅ BON — package.json stable = cache npm préservé
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./         # rarement modifié
RUN npm ci                   # layer caché si package.json inchangé
COPY . .                      # souvent modifié — en dernier

# BuildKit cache mounts (Node.js)
RUN --mount=type=cache,target=/root/.npm npm ci

# BuildKit cache mounts (Python)
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt

4. Sécurité des conteneurs

USER non-root

# Créer et utiliser un utilisateur non-root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

# Pour Node.js — user 'node' inclus dans l'image officielle
USER node
COPY --chown=node:node . .

Secrets Docker

# ❌ JAMAIS — secret dans une variable ENV ou ARG
ENV DATABASE_PASSWORD=secret123  # visible dans docker inspect !

# ✅ BuildKit secrets — non persistés dans les layers
RUN --mount=type=secret,id=db_pass \
    DB_PASS=$(cat /run/secrets/db_pass) \
    && echo "Connected"

# Lancer le build avec le secret
docker build --secret id=db_pass,src=.secrets/db_pass .

# Runtime : injecter via -e ou --env-file (jamais dans l'image)
docker run --env-file .env monapp

.dockerignore

# Exclure fichiers sensibles du contexte de build
.env
.env.*
*.pem
*.key
secrets/
.git/
node_modules/
coverage/
.DS_Store

5. Scan de vulnérabilités

# Docker Scout (intégré dans Docker Desktop)
docker scout cves monapp:latest      # Lister les CVEs
docker scout quickview monapp       # Vue d'ensemble
docker scout recommendations monapp # Recommandations

# Trivy (outil open-source)
trivy image monapp:latest

# Snyk
snyk container test monapp:latest

6. Checklist production

  • Utiliser multi-stage builds pour les apps compilées
  • Toujours spécifier des tags précis (pas :latest en prod)
  • Utiliser USER non-root dans le Dockerfile
  • Activer HEALTHCHECK pour les services critiques
  • Définir des limites de mémoire et CPU
  • Utiliser .dockerignore pour exclure les fichiers sensibles
  • Ne jamais stocker de secrets dans les layers
  • Scanner les images avec Docker Scout / Trivy en CI/CD
  • Fixer les versions des images de base et des dépendances
  • Utiliser --read-only quand possible
# HEALTHCHECK — conteneur auto-reporté
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD curl -f http://localhost:3000/health || exit 1