Changer le thème

Déploiement Docker Compose en production : trois piliers — healthcheck, restart et limites de ressources

Easton editorial illustration: input-process-output transport line

Une alerte SMS vous arrache du sommeil.

Vous ouvrez l’ordinateur : le conteneur affiche running, pastille verte, tout semble sain. Mais le service ? 502 Bad Gateway.

Le conteneur base de données n’avait pas fini de démarrer quand l’application s’est déjà précipitée pour s’y connecter. Échec de connexion, service down. Le conteneur « tourne » encore, mais le service est mort.

C’est ce qui m’est arrivé la première fois que j’ai déployé Docker Compose en production.

Faire tourner et faire tourner de façon stable, ce n’est pas la même chose. docker-compose up en un clic ne garantit pas que tout tienne debout à 3 h du matin quand la mémoire explose ou qu’un processus crash.

Les trois piliers du déploiement Docker Compose en production — healthcheck, politique de redémarrage, limites de ressources — comblent précisément ce fossé. Cet article partage la méthode de configuration tirée de mes galères, avec un modèle YAML prêt à copier, pour passer de « ça tourne » à « ça tient la route ».

1. Healthcheck : vérifier que le conteneur est réellement utilisable

L’état running de docker ps signifie seulement que le processus du conteneur est encore là. Est-ce qu’il sert correctement ? Aucune idée.

Le healthcheck, c’est Docker qui « examine » régulièrement votre conteneur : une requête HTTP, un ping base de données, ou un script — pour voir si le service est vraiment vivant.

Comment fonctionne le healthcheck ?

Docker envoie la commande de vérification à l’intervalle défini. Retour 0 = sain, non nul = malsain. Après plusieurs échecs consécutifs, le conteneur passe en unhealthy.

Point clé : un échec de healthcheck ne déclenche pas automatiquement un redémarrage. Il expose seulement l’état. Pour l’auto-récupération, combinez-le avec depends_on conditionnel et la politique de redémarrage.

Quatre paramètres à maîtriser

healthcheck:
  test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
  interval: 30s      # intervalle entre les vérifications
  timeout: 10s       # délai max. d'une vérification
  retries: 3         # échecs consécutifs avant unhealthy
  start_period: 60s  # période de grâce, échecs non comptés dans retries

J’avais ignoré start_period un temps : démarrage lent, 40 s pour la base, healthcheck dès 10 s. Trois échecs, unhealthy direct. Avec start_period: 60s, l’application a le temps de s’initialiser.

Commandes healthcheck courantes

Service Web :

healthcheck:
  test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 30s

PostgreSQL :

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U postgres"]
  interval: 10s
  timeout: 5s
  retries: 5

Redis :

healthcheck:
  test: ["CMD", "redis-cli", "ping"]
  interval: 10s
  timeout: 3s
  retries: 3

Démarrage conditionnel avec depends_on

Là où le healthcheck brille vraiment : attendre que les dépendances soient prêtes avant de lancer le service.

services:
  app:
    depends_on:
      db:
        condition: service_healthy  # attendre le healthcheck DB
      redis:
        condition: service_healthy  # attendre le healthcheck Redis

Avant, j’utilisais depends_on: [db, redis] : l’app démarrait pendant que la base s’initialisait encore. Connexion impossible, crash. Avec condition: service_healthy, l’app attend que pg_isready réponde. Enfin du calme.

2. Politique de redémarrage (restart) : auto-réparation

Conteneur tombé, qui le relève ?

Un docker restart manuel à 3 h du matin quand l’alerte sonne ? Bon courage.

La politique de redémarrage délègue ça au démon Docker : après une sortie du conteneur, Docker décide s’il faut redémarrer.

Comparaison des quatre politiques

PolitiqueComportementCas d’usage
noPas de redémarrageTests temporaires, CI/CD
alwaysRedémarrage quelle que soit la causeServices critiques
on-failureRedémarrage seulement en cas d’erreurConteneurs de tâches
unless-stoppedToujours redémarrer sauf arrêt manuelChoix production

Production : unless-stopped

restart: unless-stopped

Pourquoi unless-stopped plutôt que always ?

La différence : comportement après un docker stop manuel.

  • always : après arrêt manuel, redémarrage auto si le serveur ou Docker redémarre
  • unless-stopped : arrêt manuel = reste arrêté

Imaginez : vous stoppez un conteneur pour maintenance, le serveur redémarre et il repart tout seul. Frustrant.

Limite de tentatives avec on-failure

on-failure accepte un nombre max. de redémarrages :

restart: on-failure:5  # max. 5 redémarrages

Après 5 échecs consécutifs au démarrage, Docker abandonne. Utile quand un crash en boucle vient d’une cause externe (base injoignable, mauvaise config) — évite la boucle infinie.

Comment choisir ? En bref

  • Services critiques (Web, API, base de données) : unless-stopped
  • Tâches de fond, scripts planifiés : on-failure
  • Dev, exécution temporaire : no

Piège : la politique de redémarrage ne gère que « faut-il redémarrer après sortie du conteneur ». Pas « le service est-il vraiment utilisable ». Pour ça, il faut le healthcheck.

3. Limites de ressources (deploy.resources) : éviter les débordements

Vous avez déjà vu un conteneur fuiter en mémoire, tout avaler, et l’OOM Killer tuer les autres conteneurs ?

Moi oui. Pas glorieux.

Les limites de ressources posent un plafond par conteneur : dépassement, kill — les autres services sont protégés.

limits vs reservations

deploy:
  resources:
    limits:
      cpus: '1.0'      # max. 1 CPU
      memory: 512M     # max. 512 Mo RAM
    reservations:
      cpus: '0.5'      # min. 0,5 CPU réservé
      memory: 256M     # min. 256 Mo garanti
  • limits : limite dure, dépassement = OOM
  • reservations : limite souple, minimum garanti au planificateur

En clair : limits = « ne pas dépasser », reservations = « garantir au minimum ».

Comment régler la limite CPU ?

cpus: '1.0'   # max. 1 cœur CPU
cpus: '0.5'   # max. 50 % d'un CPU
cpus: '2.0'   # max. 2 cœurs

La limite CPU est souple : dépassement = throttling, pas de kill. Mieux vaut viser un peu haut.

Comment régler la limite mémoire ?

memory: 512M    # 512 Mo
memory: 2G      # 2 Go

La limite mémoire est dure. Dépassement = OOM Killer, sans négociation.

Mes repères :

  • Application Node.js : au moins 512M, 1G en production
  • Application Python : 256M - 512M
  • PostgreSQL : 1G - 4G selon connexions et volume
  • Redis : 256M - 512M, plus si gros cache

Configuration concrète

services:
  app:
    image: myapp:latest
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          cpus: '0.25'
          memory: 256M

L’app peut utiliser au max. 1 CPU et 1 Go RAM ; Docker garantit au minimum 0,25 CPU et 256 Mo.

Note : deploy vise surtout Docker Swarm. En mono-serveur avec docker-compose up, les limites s’appliquent avec Docker Compose V2 ou docker-compose --compatibility. Alternative historique : mem_limit et cpus (dépréciés) ; deploy est supporté depuis Compose V2.20+.

4. Modèle complet : YAML production prêt à copier

Chaque pilier seul aide ; ensemble, c’est du niveau production. Exemple complet Web + PostgreSQL + Redis, à adapter.

Exemple complet

version: '3.8'

services:
  # Application Web
  app:
    image: myapp:latest
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          cpus: '0.25'
          memory: 256M
    logging:
      driver: json-file
      options:
        max-size: "10m"   # 10 Mo max. par fichier de log
        max-file: "3"     # 3 fichiers de log conservés

  # Base PostgreSQL
  db:
    image: postgres:15
    restart: unless-stopped
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: apppassword
      POSTGRES_DB: appdb
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
        reservations:
          cpus: '0.5'
          memory: 512M
    volumes:
      - pgdata:/var/lib/postgresql/data

  # Cache Redis
  redis:
    image: redis:7-alpine
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          memory: 128M

volumes:
  pgdata:

Séparation des configurations

Dev et production diffèrent souvent. Deux fichiers :

# compose.yaml - environnement de développement
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    restart: "no"  # pas de redémarrage auto en dev
# compose.production.yaml - surcharge production
version: '3.8'
services:
  app:
    image: myapp:v1.2.3  # image buildée en production
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 1G

Lancement production :

docker-compose -f compose.yaml -f compose.production.yaml up -d

Les fichiers fusionnent ; compose.production.yaml écrase compose.yaml.

Logs : éviter de saturer le disque

Le driver par défaut json-file fait croître les logs sans limite. Sans plafond, le disque est plein en quelques mois.

logging:
  driver: json-file
  options:
    max-size: "10m"   # 10 Mo max. par fichier
    max-file: "3"     # 3 fichiers, 30 Mo max. au total

30 Mo de logs max. par conteneur, rotation auto. Je mets ça sur chaque service pour ne pas nettoyer à la main plus tard.

Résumé

En synthèse, la logique des trois piliers :

Healthcheck détecte → redémarrage répare → limites empêchent la propagation

Avec ce trio, votre stack Docker Compose peut :

  1. Attendre que les dépendances soient prêtes au démarrage, au lieu de foncer tête baissée
  2. Se relever seule après un crash, sans vous réveiller la nuit
  3. Empêcher qu’un conteneur déréglé ne fasse tomber tout le serveur

Allez vérifier votre docker-compose.yml. Il manque quoi ? Complétez.

Si vous n’avez pas encore déployé Docker Compose en production, emportez ces trois configs au prochain déploiement. Vous vous remercierez.

Configurer les trois piliers du déploiement Docker Compose en production

Ajouter healthcheck, politique de redémarrage et limites de ressources à Docker Compose pour un déploiement stable en production

⏱️ Estimated time: 15 min

  1. 1

    Step 1: Ajouter la configuration healthcheck

    Ajoutez healthcheck à chaque service :

    • test : commande de vérification (curl, pg_isready, redis-cli ping, etc.)
    • interval : intervalle entre les vérifications (10-30 s recommandé)
    • timeout : délai d'expiration (5-10 s recommandé)
    • retries : nombre d'échecs avant unhealthy (3-5 recommandé)
    • start_period : période de grâce au démarrage (30-60 s selon le temps de boot)
  2. 2

    Step 2: Configurer le démarrage conditionnel des dépendances

    Utilisez le paramètre condition de depends_on :

    • Remplacez depends_on: [db] par depends_on: db: condition: service_healthy
    • Assurez-vous que le healthcheck est configuré sur les services dépendants
    • L'application attend que les dépendances soient réellement disponibles avant de démarrer
  3. 3

    Step 3: Définir la politique de redémarrage

    Choisissez la politique selon le type de service :

    • Services critiques (Web/API/base de données) : restart: unless-stopped
    • Tâches de fond / scripts planifiés : restart: on-failure:5
    • Développement / débogage : restart: "no"
    • Évitez always (redémarrage inattendu après un arrêt manuel)
  4. 4

    Step 4: Configurer les limites de ressources

    Définissez limits et reservations dans deploy.resources :

    • limits : limite dure, dépassement = OOM Killer
    • reservations : limite souple, minimum garanti par Docker
    • Application Node.js : limits.memory d'au moins 512M
    • Base de données : 1G-4G selon connexions et volume de données
  5. 5

    Step 5: Ajouter la rotation des logs

    Évitez que les logs saturent le disque :

    • logging.driver: json-file (driver par défaut)
    • logging.options.max-size: "10m" (10 Mo max. par fichier)
    • logging.options.max-file: "3" (3 fichiers conservés)
    • Plafond total 30 Mo, rotation automatique

FAQ

Un échec de healthcheck redémarre-t-il automatiquement le conteneur ?
Non. Le healthcheck marque seulement le conteneur comme unhealthy, sans déclencher de redémarrage. Il faut le combiner avec une politique de redémarrage (ex. unless-stopped) et la condition service_healthy de depends_on. C'est la politique de redémarrage qui intervient quand le processus du conteneur s'arrête.
Quelle différence entre unless-stopped et always ?
La différence clé concerne le comportement après un arrêt manuel : avec always, après un docker stop, le conteneur redémarre automatiquement si le serveur ou le service Docker redémarre ; avec unless-stopped, un arrêt manuel le laisse arrêté. En production, unless-stopped est recommandé.
Quelle différence entre limits et reservations dans les limites de ressources ?
limits est une limite dure : dépasser limits.memory déclenche l'OOM Killer ; reservations est une limite souple qui indique au planificateur Docker le minimum requis, mais le conteneur peut consommer plus. La limite CPU est souple (throttling), la limite mémoire est dure (kill).
À quoi sert le paramètre start_period ?
start_period est une période de grâce au démarrage. Pendant ce délai, les échecs de healthcheck ne comptent pas dans retries. Pour une application lente au boot (ex. 40 s pour se connecter à la base), start_period: 60s évite un unhealthy prématuré.
Comment utiliser des configurations différentes en dev et en production ?
Séparez avec deux fichiers :

• compose.yaml : configuration dev (build: ., restart: "no")
• compose.production.yaml : surcharge production (image: xxx, restart: unless-stopped)
• Commande : docker-compose -f compose.yaml -f compose.production.yaml up -d
• Le second fichier écrase le premier, séparation des configs
Peut-on utiliser deploy.resources en déploiement mono-serveur ?
Oui. Bien que deploy soit surtout pensé pour Docker Swarm, Docker Compose V2.20+ le supporte en mono-serveur. Sur les anciennes versions, ajoutez --compatibility ou utilisez les paramètres dépréciés mem_limit/cpus. Mise à jour vers la dernière version de Docker Compose recommandée.

7 min de lecture · Publié le: 24 avr. 2026 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog