Aller au contenu principal
DockerConteneursFormation

Docker en production : logs et healthchecks

30 min de lecture Apprendre Docker — Chapitre 7

Configure Docker pour la production : conteneurs rootless, limites de ressources, health checks, logging et secrets.

🎯 Objectif : À la fin de ce chapitre, tu sauras configurer les health checks, centraliser les logs et sécuriser tes conteneurs pour la production. ⏱️ Durée estimée : 50 minutes | Niveau : Avancé

Ton conteneur tourne. docker ps affiche “Up 3 hours”. Tout va bien ? Pas forcément. L’application à l’intérieur peut être plantée, les logs peuvent remplir ton disque en silence, et ton process tourne peut-être en root sans que tu le saches.

En dev, on s’en fiche. En production, chacun de ces points peut transformer un dimanche tranquille en nuit blanche. Ce chapitre t’apprend à passer d’un conteneur qui “tourne” à un conteneur qui tourne bien — avec des health checks qui détectent les vrais problèmes, des logs maîtrisés et des bases de sécurité solides.

Pourquoi c’est important

Un conteneur Docker en production sans health check, c’est comme une voiture sans tableau de bord : tu roules, mais tu ne sais pas si le moteur surchauffe. Le jour où ça casse, tu l’apprends par tes utilisateurs.

🔥 Cas réel : Une startup SaaS avait 12 conteneurs en production. Un mardi, l’API principale a crashé silencieusement — le process Node.js avait paniqué sur une erreur mémoire, mais le conteneur restait “Up” parce que le PID 1 (tini) tournait toujours. Résultat : 45 minutes de downtime avant qu’un développeur remarque les plaintes sur Slack. Un simple health check aurait détecté le problème en 30 secondes.

Les trois piliers d’un conteneur production-ready :

  • Health checks — savoir si l’application fonctionne vraiment
  • Logs structurés — pouvoir diagnostiquer un problème à 3h du matin
  • Sécurité de base — ne pas laisser de portes ouvertes

Comprendre le concept

Health checks

Un health check, c’est une commande que Docker exécute régulièrement dans ton conteneur pour vérifier que l’application répond. Si elle échoue plusieurs fois, le conteneur passe en état unhealthy. Docker gère trois états de santé : starting (démarrage en cours), healthy (tout va bien) et unhealthy (l’application ne répond plus).

L’orchestrateur (Compose, Swarm, Kubernetes) peut alors décider de redémarrer le conteneur ou de le retirer du load balancer. Tu définis un health check soit dans le Dockerfile, soit dans le docker-compose.yml, avec quatre paramètres : l’intervalle entre les vérifications, le timeout, le nombre de retries et le délai de grâce au démarrage.

Logging

Par défaut, Docker capture tout ce que ton application écrit sur stdout et stderr, et le stocke en JSON sur le disque. Sans configuration, ces fichiers grossissent sans limite jusqu’à remplir ta partition. En production, tu veux deux choses : une rotation automatique (pour ne pas exploser le disque) et des logs structurés (pour pouvoir chercher efficacement).

💡 Tip DevOps : Configure toujours la rotation des logs au niveau du daemon Docker (/etc/docker/daemon.json), pas conteneur par conteneur. Un seul conteneur oublié peut remplir ton disque en quelques jours.

Sécurité de base

Par défaut, le process dans un conteneur tourne en root. Si un attaquant s’échappe du conteneur, il hérite de ces droits sur l’hôte. La solution : un USER non-root dans le Dockerfile, no-new-privileges dans Compose, et un filesystem en lecture seule. Deux lignes qui changent tout en cas de compromission.

Commandes essentielles

Voici un Dockerfile production-ready qui combine health check, utilisateur non-root et bonnes pratiques. Le HEALTHCHECK vérifie toutes les 30 secondes que l’endpoint /health répond, avec un délai de grâce de 15 secondes au démarrage :

FROM node:22-alpine
RUN addgroup -S app && adduser -S app -G app
WORKDIR /app
COPY --chown=app:app package*.json ./
RUN npm ci --omit=dev
COPY --chown=app:app . .
USER app
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
  CMD wget --spider -q http://localhost:3000/health || exit 1
EXPOSE 3000
CMD ["node", "server.js"]

Pour vérifier l’état de santé et monitorer les ressources, ces commandes te donnent une vue complète de tes conteneurs en cours d’exécution :

# État du health check
docker inspect --format='{{.State.Health.Status}}' mon-api

# Historique des derniers checks
docker inspect --format='{{json .State.Health}}' mon-api | jq '.Log[-3:]'

# Ressources et santé en temps réel
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
docker ps --format "table {{.Names}}\t{{.Status}}"

La configuration globale des logs se fait dans /etc/docker/daemon.json. Ce fichier applique la rotation à tous les conteneurs, même ceux lancés sans options de log :

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "5",
    "tag": "{{.Name}}"
  }
}

⚠️ Attention : Après avoir modifié daemon.json, tu dois redémarrer Docker avec sudo systemctl restart docker. Ça redémarre tous les conteneurs qui n’ont pas restart: always. Planifie ça en fenêtre de maintenance.

Cas concret : API e-commerce en production

Tu déploies une API e-commerce avec PostgreSQL et Redis. Voici un docker-compose.yml production-ready qui combine health checks, limites de ressources, sécurité et gestion des secrets :

services:
  api:
    image: mon-ecommerce:latest
    deploy:
      resources:
        limits:
          cpus: "1.5"
          memory: 512M
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 15s
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    security_opt:
      - no-new-privileges:true
    read_only: true
    tmpfs:
      - /tmp

  postgres:
    image: postgres:16-alpine
    deploy:
      resources:
        limits:
          memory: 1G
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
      interval: 10s
      timeout: 5s
      retries: 5
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    volumes:
      - pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    deploy:
      resources:
        limits:
          memory: 256M
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3

secrets:
  db_password:
    file: ./secrets/db_password.txt

volumes:
  pgdata:

Points clés de cette configuration :

  • L’API attend que PostgreSQL et Redis soient healthy avant de démarrer (condition: service_healthy)
  • Les limites mémoire empêchent un conteneur de tuer les autres en cas de fuite
  • Le mot de passe utilise un secret monté dans /run/secrets/ plutôt qu’une variable d’environnement visible dans docker inspect
  • Le filesystem est en lecture seule avec un tmpfs pour les fichiers temporaires
  • no-new-privileges bloque toute escalade de privilèges

🧠 À retenir : depends_on sans condition: service_healthy ne vérifie que si le conteneur est démarré, pas si le service est prêt. PostgreSQL peut mettre plusieurs secondes à initialiser sa base — sans cette condition, ton API crashe au démarrage.

Pièges fréquents

Le health check qui ment. Vérifier que le port est ouvert (nc -z localhost 3000) ne prouve rien — ton app peut écouter sur le port sans traiter de requêtes. Teste toujours un endpoint applicatif comme /health qui vérifie aussi la connexion à la base de données.

Les logs qui remplissent le disque. Sans rotation configurée, un conteneur actif génère des Go de logs en quelques jours. Vérifie l’espace utilisé avec du -sh /var/lib/docker/containers/*/. La configuration dans daemon.json est ta première ligne de défense.

⚠️ Attention : Ne confonds pas --memory-swap et --memory. Si tu mets --memory=512m sans toucher au swap, Docker autorise 512 Mo de RAM plus 512 Mo de swap, soit 1 Go au total. Pour désactiver le swap, mets --memory-swap=512m (identique à --memory).

Les secrets en variables d’environnement. docker inspect affiche toutes les variables d’environnement en clair. Utilise les Docker secrets (montés dans /run/secrets/) pour tout ce qui est sensible. Les images officielles PostgreSQL, MySQL et MariaDB supportent nativement les variables *_FILE.

Le depends_on naïf. Sans condition: service_healthy, ton API démarre dès que le conteneur PostgreSQL est lancé — pas quand la base est prête. Résultat : des erreurs “connection refused” au démarrage.

💡 Tip DevOps : Crée un script healthcheck.sh dans ton image plutôt qu’une commande inline. Tu peux y vérifier la connexion à la DB, l’espace disque et la réponse de l’API en un seul check — bien plus robuste qu’un simple curl.

Exercice pratique

Lance un conteneur Nginx production-ready avec health check, limites de ressources et rotation des logs :

docker run -d \
  --name nginx-prod \
  --memory=128m \
  --cpus=0.5 \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  --health-cmd="curl -f http://localhost/ || exit 1" \
  --health-interval=10s \
  --health-retries=3 \
  nginx:alpine

Ensuite, vérifie que tout est en place :

  1. docker ps — observe le statut passer de (health: starting) à (healthy)
  2. docker stats --no-stream — confirme la limite mémoire à 128 Mo
  3. docker inspect --format='{{.State.Health.Status}}' nginx-prod — doit afficher healthy
  4. Simule une panne : docker exec nginx-prod nginx -s stop et observe le passage en unhealthy
  5. docker logs nginx-prod — vérifie que les logs sont bien capturés

🧠 À retenir :

  • Health checks : vérifie un endpoint applicatif, pas juste un port ouvert
  • Logs : rotation dans daemon.json, pas conteneur par conteneur
  • Sécurité : USER non-root + no-new-privileges + read_only
  • Ressources : limites mémoire sur tous les conteneurs, sans exception
  • Secrets : /run/secrets/ plutôt que des variables d’environnement
  • Dépendances : depends_on avec condition: service_healthy, toujours

🔥 Cas réel : Les équipes SRE estiment que 60% des incidents Docker en production viennent de trois causes : pas de health check (incident non détecté), pas de limite mémoire (un conteneur tue les autres), et pas de rotation de logs (disque plein). Ce chapitre couvre les trois.


➡️ La suite : Dans le prochain chapitre, on aborde le scanning de vulnérabilités, le monitoring et les best practices de déploiement. On continue ! 🚀

🖥️ Pratique sur ton propre serveur

Pour suivre Apprendre Docker en conditions réelles, tu as besoin d'un VPS. DigitalOcean offre 200$ de crédit gratuit pour démarrer.

Obtenir 200$

Articles liés