Limites de ressources, healthchecks et politiques de redémarrage Docker Compose

13 min de lecture·Matthieu·vpsproductioncontainersdocker-composedocker|

Votre fichier Compose fonctionne en dev mais n'est pas prêt pour la production. Apprenez à ajouter des limites mémoire/CPU, des healthchecks, des politiques de redémarrage et un ordonnancement de démarrage pour protéger votre VPS contre les OOM kills et les pannes en cascade.

Un fichier Compose qui exécute docker compose up sans erreur n'est pas prêt pour la production. Sans limites de ressources, un seul conteneur peut consommer toute la mémoire de l'hôte et déclencher le OOM killer de Linux, faisant tomber chaque service du VPS. Sans healthchecks, Docker n'a aucun moyen de détecter un processus bloqué. Sans politique de redémarrage, un conteneur planté reste mort jusqu'à ce que vous le remarquiez.

Ces trois systèmes fonctionnent ensemble : les limites de ressources empêchent la consommation incontrôlée, les healthchecks détectent les pannes, et les politiques de redémarrage assurent la récupération. Ce guide couvre les trois comme une couche intégrée de durcissement pour Docker Compose v2.

Prérequis : une installation fonctionnelle de Docker Compose sur un VPS. Une familiarité avec la structure de base d'un fichier Compose. Pour un rappel, consultez .

Comment définir les limites mémoire et CPU dans Docker Compose ?

Utilisez la clé deploy.resources.limits dans la définition de votre service. Définissez memory avec une valeur comme 512M ou 1G pour créer un plafond strict. Définissez cpus avec une chaîne décimale comme '0.5' pour un demi-cœur. Le processus du conteneur est tué par l'OOM killer s'il dépasse la limite mémoire. Les limites CPU ralentissent le processus au lieu de le tuer.

services:
  api:
    image: node:22-slim
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

Ce que cela fait : le conteneur api peut utiliser au maximum 1 cœur CPU et 512 Mo de RAM. Docker garantit qu'au moins 0,25 cœur et 256 Mo sont disponibles pour lui, même quand d'autres conteneurs se disputent les ressources.

Vérifiez que les limites sont appliquées :

docker compose up -d
docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}\t{{.CPUPerc}}"

La colonne MEM USAGE / LIMIT affiche l'utilisation actuelle et le plafond configuré. Vous devriez voir 512MiB comme limite, pas la RAM totale de l'hôte. Si vous voyez la mémoire totale de l'hôte, les limites ne sont pas actives.

Quelle est la différence entre limits et reservations ?

Les limits (limites) sont des plafonds stricts. Le conteneur ne peut pas les dépasser. Les reservations (réservations) sont des garanties souples. Docker les utilise pour les décisions de planification et la gestion de la pression mémoire.

Paramètre Rôle Quand c'est utile
limits.memory Plafond strict. OOM kill si dépassé. Toujours. Empêche les conteneurs incontrôlés.
limits.cpus Limitation. Le processus ralentit. Charges lourdes en CPU (builds, inférence).
reservations.memory Minimum garanti. Hôte sous pression mémoire.
reservations.cpus Part CPU garantie. Plusieurs conteneurs gourmands en CPU.

Sans reservations, Docker alloue les ressources au premier arrivé. Sous pression, n'importe quel conteneur peut être privé de ressources. Configurez les reservations au minimum dont votre service a besoin pour fonctionner.

Que se passe-t-il quand un conteneur Docker dépasse sa limite mémoire ?

Le OOM-killer du noyau Linux termine le processus principal du conteneur avec SIGKILL. Docker enregistre le code de sortie 137 (128 + 9, où 9 correspond à SIGKILL). Si la politique de redémarrage est on-failure ou always, Docker redémarre le conteneur automatiquement.

Vérifiez si un conteneur a été victime d'un OOM kill :

docker inspect api-1 --format '{{.State.OOMKilled}}'

Sortie : true confirme un OOM kill.

Pour plus de détails :

docker inspect api-1 --format '{{json .State}}' | python3 -m json.tool

Cherchez "OOMKilled": true, "ExitCode": 137, et "RestartCount" pour voir combien de fois le conteneur a redémarré.

Sans limites, le conteneur alloue de la mémoire jusqu'à épuisement de l'hôte. Le noyau tue alors des processus à l'échelle du système pour libérer de la RAM. Cela peut faire tomber votre base de données, votre reverse proxy ou votre daemon SSH. Les limites confinent le rayon d'impact au conteneur fautif.

Comment planifier le budget de ressources des conteneurs sur un VPS ?

Sur un VPS avec des ressources fixes, vous devez répartir la mémoire entre tous les conteneurs. Laissez une marge pour le système d'exploitation hôte et Docker lui-même.

Exemple de budget pour un VPS de 8 Go :

Composant Mémoire
OS hôte + moteur Docker 1 Go
PostgreSQL 2 Go
Redis 512 Mo
API Node.js 1 Go
Nginx 128 Mo
Worker en arrière-plan 1 Go
Marge (non allouée) 2,35 Go

Gardez 20 à 30 % non alloués comme marge. Si la somme des limites de vos conteneurs dépasse la RAM totale de l'hôte, vous risquez que le OOM-killer de l'hôte intervienne, ce qui ignore les frontières de Docker.

Vérifiez l'allocation totale par rapport à la mémoire de l'hôte :

free -h
docker stats --no-stream --format "{{.Name}}: {{.MemUsage}}"

Comment configurer un healthcheck dans Docker Compose ?

Ajoutez un bloc healthcheck à la définition de votre service. Docker exécute la commande de test à l'intervalle spécifié et marque le conteneur comme unhealthy après le nombre configuré d'échecs consécutifs. D'autres services peuvent dépendre de cet état de santé pour l'ordonnancement du démarrage.

services:
  api:
    image: node:22-slim
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
      start_interval: 2s

Paramètres du healthcheck :

Paramètre Par défaut Recommandé Rôle
interval 30s 15-60s Délai entre les vérifications après le démarrage
timeout 30s 5-10s Durée max d'une vérification
retries 3 3-5 Échecs avant unhealthy
start_period 0s 10-60s Période de grâce pour les services lents au démarrage
start_interval 5s 2-5s Intervalle pendant le démarrage (Compose v2.20+)

Le paramètre start_interval (ajouté dans Compose v2.20) permet de vérifier plus fréquemment pendant le démarrage. Le conteneur passe de starting à healthy dès que la première vérification réussit pendant le start_period. Ensuite, les vérifications s'exécutent à l'interval normal.

Quelle est la différence entre CMD et CMD-SHELL dans les healthchecks ?

CMD exécute la commande directement, sans shell. CMD-SHELL la passe par /bin/sh -c. Privilégiez CMD quand c'est possible. Cela évite la surcharge du shell et élimine les problèmes de PID 1 où le shell, et non votre commande de vérification, reçoit les signaux.

# Format CMD - pas de shell, exécute le binaire directement
healthcheck:
  test: ["CMD", "pg_isready", "-U", "postgres"]

# Format CMD-SHELL - passe par /bin/sh -c
healthcheck:
  test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]

# Raccourci en chaîne - équivalent à CMD-SHELL
healthcheck:
  test: curl -f http://localhost:3000/health || exit 1

Utilisez CMD-SHELL quand vous avez besoin de fonctionnalités du shell comme ||, les pipes ou l'expansion de variables. Utilisez CMD pour une simple exécution de binaire.

Quel healthcheck utiliser pour PostgreSQL, Redis et Nginx ?

Chaque service a besoin d'un healthcheck qui teste la capacité du service à traiter des requêtes, pas seulement si le processus tourne.

Service Commande healthcheck Ce qu'elle teste
PostgreSQL ["CMD", "pg_isready", "-U", "postgres"] Accepte les connexions
Redis ["CMD", "redis-cli", "ping"] Répond aux commandes
Nginx `["CMD-SHELL", "curl -f http://localhost/
Application Node.js `["CMD-SHELL", "curl -f http://localhost:3000/health
MySQL/MariaDB ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] Moteur prêt, pas juste le socket ouvert

Attention : pour les healthchecks à base de curl, l'image doit inclure curl. Beaucoup d'images slim ne l'ont pas. Installez-le dans votre Dockerfile, utilisez wget à la place, ou écrivez un endpoint de santé minimal que votre framework applicatif sert nativement.

Vérifiez le statut du healthcheck :

docker compose ps

Cherchez (healthy) ou (unhealthy) dans la colonne STATUS. Pour l'historique détaillé :

docker inspect api-1 --format '{{json .State.Health}}' | python3 -m json.tool

Cela affiche les derniers résultats de vérification, avec les sorties stdout/stderr des vérifications échouées. Ouvrez l'œil : si FailingStreak continue d'augmenter, votre commande de vérification est erronée ou le service est réellement en panne.

Comment les politiques de redémarrage interagissent-elles avec les healthchecks ?

Les politiques de redémarrage contrôlent ce que Docker fait quand un conteneur s'arrête. Les healthchecks contrôlent comment Docker détecte les problèmes dans les conteneurs en cours d'exécution. Ensemble, ils créent une boucle de récupération automatisée : le healthcheck détecte la panne, le conteneur est arrêté, et la politique de redémarrage le relance.

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

Comparaison des politiques de redémarrage

Politique En cas de crash Au redémarrage Sur docker stop Idéal pour
no Reste mort Reste mort Reste mort Tâches ponctuelles, débogage
always Redémarre Redémarre Redémarre Infrastructure de base (BDD, proxys)
unless-stopped Redémarre Redémarre Reste mort La plupart des services en production
on-failure:N Redémarre (jusqu'à N fois) Reste mort Reste mort Services qui ne doivent pas redémarrer indéfiniment

on-failure:5 signifie que Docker réessaie jusqu'à 5 fois. Après quoi, le conteneur reste mort. Cela empêche les boucles de redémarrage qui gaspillent du CPU sur un service fondamentalement cassé.

L'interaction OOM + redémarrage : quand un conteneur atteint sa limite mémoire et subit un OOM kill (code de sortie 137), Docker traite cela comme un échec. Avec on-failure:5, il redémarre jusqu'à 5 fois. Si le service a une fuite mémoire, il sera tué et redémarré de façon répétée jusqu'à atteindre la limite de tentatives. Vérifiez le compteur de redémarrages :

docker inspect api-1 --format '{{.RestartCount}}'

Pour la plupart des services en production, utilisez unless-stopped. Il redémarre en cas de crash et de redémarrage de l'hôte, mais respecte les commandes manuelles docker compose stop. Utilisez on-failure:N pour les services où une boucle de crash doit déclencher une alerte, pas retenter silencieusement à l'infini.

Comment faire attendre un service qu'un autre service soit sain ?

Utilisez depends_on avec condition: service_healthy. Docker Compose attend alors que le healthcheck de la dépendance réussisse avant de démarrer le service dépendant.

services:
  db:
    image: postgres:17
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "postgres"]
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s
    restart: unless-stopped

  api:
    image: myapp:latest
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 3
    restart: unless-stopped

Sans condition: service_healthy, depends_on attend seulement que le conteneur démarre, pas que le service qu'il contient soit prêt. PostgreSQL met plusieurs secondes à s'initialiser. Votre application planterait en essayant de se connecter à une base de données qui n'accepte pas encore les connexions.

L'option restart: true à l'intérieur de depends_on (Compose v2.21+) indique à Docker de redémarrer le service dépendant si la dépendance redémarre :

depends_on:
  db:
    condition: service_healthy
    restart: true

C'est utile quand votre application met en cache la connexion à la base de données et ne peut pas se rétablir d'un redémarrage de la BDD sans un redémarrage complet.

Quels ulimits définir pour les conteneurs en production ?

Configurez nofile (descripteurs de fichiers ouverts) et nproc (nombre max de processus) pour les services qui gèrent beaucoup de connexions simultanées. Chaque connexion TCP, fichier ouvert et pipe consomme un descripteur de fichier. La limite par défaut (1024 dans beaucoup d'images) est trop basse pour les bases de données et les services à fort trafic.

services:
  db:
    image: postgres:17
    ulimits:
      nofile:
        soft: 65536
        hard: 65536
      nproc:
        soft: 4096
        hard: 4096

Vérifiez à l'intérieur du conteneur :

docker compose exec db cat /proc/1/limits

Cherchez Max open files et Max processes. Les valeurs doivent correspondre à celles que vous avez définies.

Prévention des fork bombs avec la limite de PIDs

Configurez deploy.resources.limits.pids pour plafonner le nombre de processus qu'un conteneur peut créer. Cela empêche les fork bombs et la création incontrôlée de processus de consommer tous les PIDs de l'hôte.

services:
  api:
    image: myapp:latest
    deploy:
      resources:
        limits:
          pids: 200

Si vous n'utilisez pas deploy.resources pour les limites CPU/mémoire, la clé de premier niveau pids_limit fonctionne aussi. Mais quand deploy.resources.limits est présent, vous devez y placer aussi la limite de PIDs. Mélanger les deux provoque une erreur de validation dans Compose v5+.

200 PIDs est généreux pour une application web typique. Une application Node.js en utilise environ 10 à 30. PostgreSQL utilise grosso modo un processus par connexion plus des workers en arrière-plan. Dimensionnez à 2-3x votre pic attendu.

Comment limiter la taille des logs des conteneurs ?

Sans limite de logs, les logs des conteneurs grossissent sans fin. Un service verbeux peut remplir votre disque en quelques heures. Configurez max-size et max-file sur le driver de journalisation json-file pour activer la rotation automatique des logs.

services:
  api:
    image: myapp:latest
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

Cela garde au maximum 3 fichiers de 10 Mo chacun, plafonnant le stockage des logs à 30 Mo par service. Ajustez selon vos besoins de débogage. Utilisez une ancre YAML pour appliquer la même configuration de logs à tous les services :

x-logging: &default-logging
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "3"

services:
  api:
    image: myapp:latest
    logging: *default-logging
  worker:
    image: myapp:latest
    logging: *default-logging

Définir stop_grace_period pour un arrêt propre

Quand Docker arrête un conteneur, il envoie SIGTERM et attend que le processus se termine proprement. Si le processus ne s'arrête pas dans le délai de grâce, Docker envoie SIGKILL. La valeur par défaut est de 10 secondes.

services:
  db:
    image: postgres:17
    stop_grace_period: 30s

  api:
    image: myapp:latest
    stop_grace_period: 5s

Les bases de données ont besoin de délais de grâce plus longs pour vider les écritures et fermer les connexions proprement. Les serveurs web et les processus d'API se terminent en général en quelques secondes. Définissez le délai de grâce en fonction du temps d'arrêt réel de votre service, avec une petite marge.

Fichier Compose complet prêt pour la production

Cet exemple combine tous les paramètres pour une stack applicative web typique : reverse proxy Nginx, API Node.js, base de données PostgreSQL et cache Redis.

x-logging: &default-logging
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "3"

services:
  nginx:
    image: nginx:1.27-alpine
    ports:
      - "80:80"
      - "443:443"
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 128M
        reservations:
          memory: 64M
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3
    restart: unless-stopped
    stop_grace_period: 5s
    logging: *default-logging
    depends_on:
      api:
        condition: service_healthy

  api:
    image: myapp:latest
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
          pids: 200
        reservations:
          cpus: '0.25'
          memory: 256M
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 15s
      start_interval: 2s
    restart: unless-stopped
    stop_grace_period: 5s
    logging: *default-logging
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy

  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 2G
        reservations:
          cpus: '0.5'
          memory: 1G
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "postgres"]
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 15s
    restart: unless-stopped
    stop_grace_period: 30s
    ulimits:
      nofile:
        soft: 65536
        hard: 65536
    logging: *default-logging
    volumes:
      - pgdata:/var/lib/postgresql/data

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

volumes:
  pgdata:

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

À noter : le mot de passe de la base de données utilise Docker secrets avec POSTGRES_PASSWORD_FILE au lieu d'une variable d'environnement POSTGRES_PASSWORD en clair. Créez le fichier de secrets avec des permissions restreintes :

mkdir -p secrets
openssl rand -base64 32 > secrets/db_password.txt
chmod 600 secrets/db_password.txt

Liste de vérification

Après avoir appliqué tous les paramètres, parcourez ces vérifications pour confirmer que tout est actif.

1. Vérifier que les limites de ressources sont appliquées :

docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}\t{{.CPUPerc}}"

La colonne MEM USAGE / LIMIT affiche l'utilisation actuelle et le plafond configuré. Chaque conteneur doit afficher sa limite mémoire configurée, pas la RAM totale de l'hôte.

2. Vérifier le statut des healthchecks :

docker compose ps

Tous les services doivent afficher (healthy) dans la colonne STATUS. Si certains affichent (health: starting), attendez que le start_period soit terminé.

3. Vérifier la politique de redémarrage :

docker inspect --format '{{.HostConfig.RestartPolicy.Name}}:{{.HostConfig.RestartPolicy.MaximumRetryCount}}' $(docker compose ps -q)

4. Vérifier les ulimits dans un conteneur :

docker compose exec db cat /proc/1/limits | grep -E "open files|processes"

5. Vérifier la configuration des logs :

docker inspect --format '{{.HostConfig.LogConfig.Type}} max-size={{index .HostConfig.LogConfig.Config "max-size"}} max-file={{index .HostConfig.LogConfig.Config "max-file"}}' $(docker compose ps -q api)

6. Tester la chaîne de récupération complète :

Arrêtez un conteneur et observez sa récupération :

docker compose stop api
docker compose ps  # api devrait afficher Exited
docker compose start api
docker compose ps  # api devrait afficher (healthy) après le start_period
docker inspect $(docker compose ps -q api) --format 'RestartCount: {{.RestartCount}}'

Pour tester le redémarrage automatique sur un vrai crash, baissez la limite mémoire en dessous du minimum de l'application. La politique de redémarrage se déclenche quand le processus se termine de lui-même. Notez que docker kill ne déclenche pas les politiques de redémarrage dans les versions récentes de Docker.

Référence rapide pour le dimensionnement des ressources

Points de départ pour les services courants sur un VPS. Ajustez selon votre charge réelle.

Service Limite mémoire Limite CPU Healthcheck
PostgreSQL 1-4 Go 1.0-2.0 pg_isready -U postgres
Redis 256M-1G 0.25-0.5 redis-cli ping
API Node.js 256M-1G 0.5-1.0 curl -f http://localhost:PORT/health
Nginx 64M-256M 0.25-0.5 curl -f http://localhost/
Ollama (LLM) 4-8 Go 2.0-4.0 curl -f http://localhost:11434/
Worker en arrière-plan 256M-1G 0.5-1.0 Vérification spécifique à l'application

Quelque chose ne fonctionne pas ?

Le conteneur redémarre en boucle :

docker compose logs api --tail 50
docker inspect api-1 --format '{{.State.ExitCode}} OOM:{{.State.OOMKilled}} Restarts:{{.RestartCount}}'

Si OOMKilled: true, augmentez la limite mémoire. Si le code de sortie n'est pas 137, vérifiez les logs applicatifs pour identifier l'erreur réelle.

Le healthcheck échoue toujours :

docker inspect api-1 --format '{{json .State.Health.Log}}' | python3 -m json.tool

Cela affiche la sortie de chaque vérification. Causes courantes : l'endpoint de santé n'existe pas, curl n'est pas installé dans l'image, ou le service écoute sur un port différent de celui ciblé par la vérification.

depends_on n'attend pas :

Assurez-vous que la dépendance a un healthcheck défini. Sans cela, condition: service_healthy n'a rien à attendre et Compose retournera une erreur au démarrage.

Les limites n'apparaissent pas dans docker stats :

Vérifiez que vous utilisez Docker Compose v2 (le plugin docker compose, pas l'ancien binaire docker-compose). Vérifiez votre version :

docker compose version

La clé deploy.resources nécessite Compose v2. Si vous êtes sur une version antérieure, consultez pour les instructions d'installation.

Lire les logs quand quelque chose échoue :

journalctl -u docker -f
docker compose logs -f --tail 100

Le log du daemon Docker affiche les événements OOM et les changements de cycle de vie des conteneurs. Les logs Compose affichent la sortie applicative.


Copyright 2026 Virtua.Cloud. Tous droits réservés. Ce contenu est une création originale de l'équipe Virtua.Cloud. Toute reproduction, republication ou redistribution sans autorisation écrite est interdite.

Prêt à essayer ?

Déployez votre serveur en quelques secondes. Linux, Windows ou FreeBSD.

Voir les offres VPS