infra / docker-compose
Dépendances entre services : depends_on et healthchecks
Explication
Ce que vous allez apprendre
- Comprendre pourquoi
depends_onseul ne garantit qu'un ordre, jamais une vraie disponibilité - Écrire un healthcheck qui teste réellement si un service répond (
pg_isready,redis-cli ping) - Choisir entre
service_started,service_healthyetservice_completed_successfully - Calibrer
start_period,intervaletretriespour éviter les faux "unhealthy" - Faire attendre un service qu'un job ponctuel (migration) se termine avec succès
Dans quel contexte ?
Sur le projet boutique-api, le service api échoue systématiquement au tout premier démarrage avec l'erreur "ECONNREFUSED 127.0.0.1:5432", puis fonctionne parfaitement après un simple docker compose restart api. Le service db n'est pas en cause : Postgres est bien "Up", mais il n'a pas encore terminé d'initialiser ses fichiers internes quand l'API tente sa première connexion. C'est exactement ce que cette leçon règle avec un healthcheck.
Un bug très courant en développement
D'abord, un scénario classique : ton API démarre, essaie de se connecter à la base de données immédiatement, et échoue. En relançant juste après, ça marche. Ce comportement étrange a une explication précise.
"Démarré" ne veut pas dire "prêt"
Un conteneur de base de données peut afficher "Up" en quelques millisecondes, alors que le moteur Postgres met encore plusieurs secondes à initialiser ses fichiers et à accepter des connexions. Le conteneur tourne, mais le service à l'intérieur n'est pas encore opérationnel.
Ce que fait vraiment depends_on (et ce qu'il ne fait pas)
Sans condition supplémentaire, depends_on garantit uniquement un ORDRE de démarrage : Docker lance db avant api, mais ne vérifie jamais que db est réellement fonctionnelle. C'est comme allumer le four et enfourner le plat immédiatement, sans attendre qu'il ait atteint la bonne température.
Il reste un problème à résoudre
Un simple ordre de démarrage ne suffit donc pas. Il faut un moyen de savoir si un service est VRAIMENT prêt à recevoir des connexions, pas juste "démarré".
La solution : le healthcheck
Un healthcheck est une commande que Docker exécute périodiquement à l'intérieur du conteneur pour juger s'il est réellement fonctionnel — par exemple pg_isready pour Postgres. C'est un test de santé répété, pas une simple vérification ponctuelle au démarrage.
Combiner les deux : depends_on + condition
En combinant depends_on avec condition: service_healthy, on force Compose à attendre que ce test de santé réussisse avant de démarrer le service suivant. Pour reprendre l'image du four : il attend maintenant d'avoir atteint sa température avant qu'on y mette le plat.
Condition depends_on | Ce qu'elle vérifie | Cas d'usage |
|---|---|---|
service_started | Le conteneur a démarré (comportement historique) | Dépendance sans vérification fine nécessaire |
service_healthy | Le healthcheck renvoie un statut OK | Base de données, cache, API interne |
service_completed_successfully | Le service s'est terminé avec un exit code 0 | Job ponctuel : migration, seed de données |
Bonne pratique
Choisis toujours un healthcheck qui teste une vraie capacité fonctionnelle, comme pg_isready pour Postgres, plutôt qu'un simple ps qui ne vérifie que la présence du process. Un process peut tourner sans être capable de répondre à une seule requête.
Une troisième condition, pour un cas particulier
Il existe une troisième option, service_completed_successfully, très différente des deux autres : elle attend qu'un service se TERMINE avec succès, plutôt que de tourner en continu. C'est utile pour des jobs ponctuels comme une migration de base de données, qu'on reverra dans une leçon dédiée.
Les pièges à connaître
Un start_period trop court sur un healthcheck déclenche de faux "unhealthy" pendant que le service s'initialise encore normalement. Et service_started, le comportement historique, ne protège de rien côté disponibilité réelle : garde-le seulement pour des dépendances qui n'ont pas besoin de vérification fine.
Piège fréquent
Un start_period de 2 secondes sur le healthcheck d'une base Postgres qui met 8 secondes à s'initialiser au premier démarrage marque le service "unhealthy" à tort, ce qui bloque tous les services qui en dépendent avec service_healthy. Mesure le temps de démarrage réel du service avant de fixer start_period.
La leçon suivante s'appuie sur ces mêmes idées d'isolation, mais côté réseau plutôt que côté démarrage.
Commandes & code
depends_on & healthchecks
depends_on sans condition ne garantit que l'ORDRE de démarrage, pas que le service dépendant soit VRAIMENT prêt.
services:
api:
build: ./api
depends_on:
db:
condition: service_healthy # attend que le healthcheck de "db" soit "healthy"
cache:
condition: service_started # attend juste que le conteneur démarre
ports:
- "3000:3000"
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: changeme
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s # laisse le temps à l'init avant de compter les échecs
cache:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3# Voir l'état de santé des conteneurs (healthy / unhealthy / starting)
docker compose ps
docker inspect --format='{{json .State.Health}}' $(docker compose ps -q db)# Cas concret : une API qui ne doit démarrer qu'après une migration réussie
services:
migrate:
build: ./api
command: ["npm", "run", "migrate"]
depends_on:
db:
condition: service_healthy
api:
build: ./api
depends_on:
migrate:
condition: service_completed_successfully # attend un exit code 0
ports:
- "3000:3000"Résumé
condition: service_started: ordre seulement (comportement historique).condition: service_healthy: attend un healthcheck OK.condition: service_completed_successfully: utile pour les jobs one-shot (migrations, seed).- Un
healthcheckmal calibré (start_periodtrop court) déclenche des fauxunhealthy.
Exercices pratiques
Mission : l'API qui échoue une fois sur deux au démarrage
Objectif : Remplacer un depends_on naïf par un healthcheck fiable et calibrer son start_period.
Contexte
Sur boutique-api, le service api déclare déjà depends_on: - db (syntaxe courte, sans condition), mais échoue encore parfois au tout premier démarrage avec "ECONNREFUSED 127.0.0.1:5432", alors que docker compose ps montrait db comme "Up". Le moteur Postgres met environ 8 secondes à s'initialiser complètement au premier démarrage.
Corrige la configuration pour que l'API attende une vraie disponibilité de la base.