Pourquoi `depends_on` par défaut échoue
Par défaut, depends_on utilise la condition service_started. Cela signifie que Docker attend uniquement que le conteneur cible soit lancé — autrement dit, que son processus principal soit démarré. Cela ne dit rien sur l'état interne du service.
PostgreSQL, comme la plupart des bases de données, traverse plusieurs phases à l'initialisation : l'image officielle exécute des scripts d'amorçage, crée les rôles, initialise les extensions et positionne le cluster avant de commencer à accepter des connexions. Cette séquence peut prendre de quelques secondes à plus de trente secondes sur un VPS avec un disque chaud, un volume non préparé ou un gros jeu d'extensions.
Pendant ce temps, votre application — qui respecte pourtant la directive depends_on — tente déjà de se connecter, et reçoit un refus net.
Les conséquences pratiques d'une race condition au démarrage
connection refused— le socket TCP de PostgreSQL n'est pas encore ouvert, l'application échoue au premier appel PDO ou SQLAlchemy.FATAL: role does not exist— PostgreSQL écoute, mais les scripts d'initialisation (docker-entrypoint-initdb.d) n'ont pas encore créé le rôle ni la base de données.FATAL: the database system is starting up— le cluster est en cours de récupération après un arrêt propre ; les connexions sont temporairement refusées.- Crash loop silencieux — Docker
restart: unless-stoppedrelance l'application indéfiniment, les logs se répètent, et le problème passe pour une erreur applicative. - Faux positifs en CI — les tests d'intégration échouent de manière intermittente selon la vitesse de démarrage du runner.
- Dépendances en cascade — une API qui dépend d'une appli qui dépend de la base hérite du même problème si la chaîne
depends_onn'est pas uniformément correcte.
Prérequis : Docker Compose v2 et le plugin officiel
La condition service_healthy n'est pas disponible dans Docker Compose v1 (le binaire docker-compose en Python, désormais obsolète). Elle est supportée depuis Docker Compose v2, distribué comme plugin Go sous la commande docker compose (sans tiret).
Pour vérifier votre version :
docker compose versionLa sortie doit afficher Docker Compose version v2.x.x ou supérieur. Sur Debian 12 et Ubuntu 22.04+, le plugin est disponible dans les dépôts officiels Docker. Si vous avez encore docker-compose (v1), migrez : le projet est archivé et ne reçoit plus de correctifs de sécurité.
Aucune dépendance supplémentaire n'est requise pour PostgreSQL : pg_isready est un outil natif de l'image officielle postgres, présent dans tous les tags depuis des années.
Configurer un healthcheck PostgreSQL fiable, étape par étape
Comprendre service_started vs service_healthy
depends_on accepte trois conditions :
- service_started (défaut) — attend que le conteneur soit simplement démarré.
- service_healthy — attend que le healthcheck du conteneur renvoie healthy.
- service_completed_successfully — pour les conteneurs à courte durée de vie (jobs, migrations).
Pour toute base de données, service_healthy est la seule condition qui garantit que le service accepte des connexions.
Écrire le healthcheck PostgreSQL dans le service `db`
Ajoutez le bloc healthcheck directement dans la définition du service db :
services:
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: appdb
healthcheck:
test: ["CMD", "pg_isready", "-U", "app", "-d", "appdb"]
interval: 5s
timeout: 5s
retries: 5
start_period: 30sLe champ test reçoit une liste : le premier élément est CMD (Docker exécute la commande directement), suivi des arguments. pg_isready renvoie 0 si PostgreSQL est prêt à accepter des connexions sur l'utilisateur et la base indiqués, et un code non nul sinon — ce que Docker interprète comme healthy ou unhealthy.
Comprendre le rôle de `start_period`
start_period est la fenêtre de grâce accordée au conteneur pour s'initialiser avant que les échecs de healthcheck ne commencent à être comptabilisés dans retries. Pendant cette fenêtre, Docker lance bien le healthcheck, mais un échec n'incrémente pas le compteur.
Sans start_period, un PostgreSQL qui met 15 secondes à s'initialiser échouerait ses 5 premiers checks (interval: 5s × 5 tentatives = 25 secondes) et passerait en unhealthy avant même d'être opérationnel.
La valeur recommandée est 30 secondes pour un PostgreSQL standard : suffisamment longue pour absorber les initialisations lentes (premier démarrage avec volume vide, extensions lourdes) sans retarder inutilement le démarrage en régime permanent. interval et start_period sont distincts : interval cadence les vérifications en régime normal, start_period protège la phase d'amorçage.
Écrire le `depends_on` avec `condition: service_healthy`
Dans chaque service qui dépend de la base, remplacez la forme courte de depends_on par la forme longue avec condition :
services:
app:
image: monapp:latest
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgresql://app:secret@db:5432/appdbAvec cette configuration, Docker attend que le healthcheck du service db renvoie healthy avant de démarrer app. Si db passe en unhealthy après retries échecs, app ne démarre pas.
Tester avec `docker compose up`
Lancez la stack et observez le séquencement :
docker compose upVous verrez dans les logs des lignes du type :
db | database system is ready to accept connections
app | Waiting for db to be healthy...
app | Starting application serverPour vérifier l'état du healthcheck à tout moment :
docker inspect <nom_du_conteneur_db> | grep -A 5 '"Health"'La sortie indique Status: healthy, starting ou unhealthy, et liste les dernières sorties du check.
Cas Redis : healthcheck adapté
Redis ne dispose pas de redis-isready, mais son équivalent est redis-cli ping :
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10sredis-cli ping renvoie PONG et le code de sortie 0 si Redis accepte les connexions. Comme Redis démarre plus vite que PostgreSQL, start_period: 10s est généralement suffisant.
Cas MySQL / MariaDB : `mysqladmin ping`
Pour MySQL ou MariaDB, utilisez mysqladmin ping :
mysql:
image: mariadb:11
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: appdb
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-psecret"]
interval: 5s
timeout: 5s
retries: 5
start_period: 30sAttention : le mot de passe est concaténé directement à -p sans espace (-psecret), c'est le comportement attendu de mysqladmin. Cette commande s'affiche dans docker inspect, donc préférez un secret Compose ou une variable d'environnement si la confidentialité est une contrainte.
Dépannage : quatre erreurs fréquentes
pg_isready: command not found — vous n'utilisez pas l'image officielle postgres (ou une image dérivée qui l'inclut). Vérifiez avec docker compose exec db which pg_isready.
Healthcheck en boucle, jamais healthy — le test ne renvoie jamais 0. Testez manuellement : docker compose exec db pg_isready -U app -d appdb. Si la commande échoue, vérifiez les variables d'environnement POSTGRES_USER et POSTGRES_DB.
start_period trop court — sur un premier démarrage avec un volume vide, PostgreSQL peut prendre plus de 30 secondes. Augmentez à 60s ou observez les logs : database system was shut down at … LOG: database system is ready to accept connections indique le délai réel.
Conteneur en unhealthy permanent — après retries échecs, Docker marque le conteneur unhealthy mais ne le redémarre pas (c'est le rôle de restart). Consultez docker inspect pour voir la sortie des derniers checks et identifier la commande qui échoue.
Comportement par défaut vs avec healthcheck
| Cas | Comportement par défaut (`service_started`) | Avec `service_healthy` |
|---|---|---|
| Premier démarrage, volume vide | App démarre avant que la base soit prête → crash loop | App attend que PostgreSQL soit initialisé et accepte les connexions |
| Redémarrage après un arrêt propre | App peut démarrer pendant la phase de recovery PostgreSQL | App reste en attente jusqu'à la fin de la recovery |
| Base lente (extensions, gros init) | Race condition selon la vitesse du host | Pas de race condition : le healthcheck valide l'état réel |
| Dépendances en cascade (app → worker → db) | Chaque maillon doit gérer lui-même les tentatives de reconnexion | La chaîne de conditions garantit l'ordre de démarrage |
| Tests d'intégration en CI | Résultats intermittents selon la vitesse du runner | Résultats déterministes |
| Redis ou MySQL à la place de PostgreSQL | Même problème, `depends_on` par défaut ne fait pas de distinction | Même solution, commande de check adaptée à chaque moteur |
`pg_isready` ou `SELECT 1` : lequel choisir ?
On voit souvent deux variantes de healthcheck PostgreSQL dans la nature :
- ["CMD", "pg_isready", "-U", "postgres"]
- ["CMD-SHELL", "psql -U postgres -c 'SELECT 1'"]"
pg_isready est plus fiable pour une raison simple : il teste uniquement la capacité du serveur à accepter des connexions TCP, sans ouvrir de session SQL. Il renvoie 0 dès que le serveur écoute et accepte la poignée de main, ce qui est exactement ce dont une application a besoin pour tenter sa propre connexion.
SELECT 1 via psql ouvre une vraie session SQL et exécute une requête. C'est un test plus profond, mais il peut échouer pour des raisons non liées à la disponibilité du serveur (quota de connexions atteint, pg_hba.conf mal configuré). Pour un healthcheck, le test minimal et direct est préférable.
Adapter le healthcheck à Redis et MySQL
Pour Redis, remplacez pg_isready par CMD redis-cli PING — la commande renvoie PONG dès que le serveur accepte des connexions. Pour MySQL ou MariaDB, utilisez CMD mysqladmin ping -h localhost -u root --password=$$MYSQL_ROOT_PASSWORD : ajustez start_period: 60s car l'initialisation d'une base MySQL prend plus de temps que PostgreSQL. Le pattern condition: service_healthy est identique quel que soit le service cible.
Maillage et prochaines étapes
Le healthcheck service_healthy est l'un des réglages de robustesse à activer en production. Plusieurs autres points méritent la même attention avant un déploiement durable : politique de redémarrage restart: unless-stopped, limites de ressources deploy.resources.limits, et rotation des logs logging.options. Retrouvez la checklist complète dans Docker Compose en production : 10 points à vérifier.
Si votre stack grandit — plusieurs services, plusieurs hôtes — un reverse proxy comme Caddy ou Traefik s'impose pour gérer le routage HTTPS. Le guide Caddy, Traefik ou Nginx Proxy Manager détaille les critères de choix selon votre profil.
Pour automatiser le déploiement de l'ensemble de l'infrastructure (VPS, Docker, configuration) de façon reproductible, Ansible pour automatiser vos serveurs VPS vous guidera pas à pas.