Dependencias entre servicios en Docker Compose: healthcheck para resolver el orden de arranque de la base de datos

Son las diez de la noche del viernes y el mismo error lleva repitiéndose en la terminal más de una docena de veces.
El contenedor de la aplicación se reinicia sin parar; la base de datos sí arranca, pero siempre un paso tarde. Revisas docker-compose.yml: depends_on está configurado. ¿Por qué sigue fallando?
Este problema ha atormentado a muchos desarrolladores. En local, con docker-compose up, las dos primeras veces casi siempre fallan; hay que esperar unos segundos y reiniciar varias veces hasta que todo arranca bien.
La causa es simple: depends_on de Docker solo controla el orden de arranque de contenedores, no si el servicio está realmente listo.
En este artículo te enseño paso a paso:
- Las tres condition de depends_on (el 90 % solo conoce la opción por defecto)
- El healthcheck correcto para PostgreSQL y MySQL (con configuración completa)
- La alternativa moderna a los scripts wait-for-it
- Una lista de diagnóstico para que tus contenedores arranquen de forma estable
Por qué depends_on no basta: arranque ≠ listo
En la documentación oficial de Docker hay una frase clave que muchos pasan por alto:
Compose does not wait until a container is “ready”, only until it’s running.
Traducción: Compose solo espera a que el contenedor arranque, no a que el servicio sea usable.
Diferencia entre arranque del contenedor y servicio listo
Imagina el arranque de un contenedor PostgreSQL:
- 0 s: Docker levanta el contenedor, arranca el proceso postgres ← depends_on da luz verde aquí
- 2 s: inicializa el directorio de datos
- 5 s: carga la configuración
- 8 s: ejecuta scripts init (si los hay)
- 12 s: por fin ready, acepta conexiones
Entre medias hay un gap de unos 12 segundos. Si tu app web intenta conectar en el segundo 1, el resultado será “Connection refused”.
En casos reales puede ser peor. Mantuve un proyecto legacy cuyo script de inicialización importaba 500 MB de datos de prueba; solo ese paso tardaba 40 segundos. Con depends_on por defecto, el contenedor de la app se caía y reiniciaba al menos cinco veces antes de conectar.
Las tres condition de depends_on
Mucha gente no sabe que depends_on admite tres condition:
services:
web:
depends_on:
db:
condition: service_started # Por defecto: basta con que el contenedor esté running
# condition: service_healthy # Espera a que pase el healthcheck
# condition: service_completed_successfully # Espera salida exitosa (ideal para init containers)
service_started (por defecto): continúa en cuanto el contenedor está en estado running. Por eso depends_on a veces no basta.
service_healthy: espera a que el healthcheck pase y el contenedor pase a “healthy”. Es lo que realmente necesitamos.
service_completed_successfully: espera a que el contenedor termine con código de salida 0. Ideal para migraciones u otras tareas puntuales.
¿Por qué service_healthy no es el valor por defecto?
Puede que te preguntes: si service_healthy funciona tan bien, ¿por qué no es el default?
Dos razones:
- No todos los servicios necesitan healthcheck (por ejemplo, workers stateless)
- El healthcheck lo configuras tú; Docker no sabe cuándo tu servicio está “listo”
Eso nos lleva al siguiente tema: cómo configurar el healthcheck.
Guía completa de healthcheck
El principio es directo: Docker ejecuta periódicamente un comando; si devuelve 0, el servicio está healthy; si devuelve 1, unhealthy.
Configuración completa de healthcheck
services:
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"] # Comando de comprobación
interval: 10s # Comprobar cada 10 segundos
timeout: 5s # Timeout por comprobación: 5 s
retries: 3 # Marcar unhealthy tras 3 fallos consecutivos
start_period: 30s # Los fallos en los primeros 30 s no cuentan para retries
Los cinco parámetros importan. Vamos uno a uno.
test: comando de comprobación
Hay dos formatos:
# Forma 1: usar shell (recomendado)
test: ["CMD-SHELL", "pg_isready -U postgres"]
# Forma 2: ejecutar comando directo (sin shell)
test: ["CMD", "pg_isready", "-U", "postgres"]
En la mayoría de casos basta CMD-SHELL, porque permite pipes, redirecciones y otras características de shell.
Trampa habitual: la herramienta del comando debe existir en la imagen. Si usas curl para comprobar HTTP pero la imagen no lo incluye, el healthcheck fallará siempre. Me pasó una vez; tardé media hora en darme cuenta de que había que añadir RUN apk add curl al Dockerfile.
interval: intervalo de comprobación
Con qué frecuencia comprobar. Muy frecuente gasta recursos; muy lento, reacciona tarde.
- 10 s es un buen valor por defecto para la mayoría de escenarios
- Servicios críticos como bases de datos: 5 s
- Servicios ligeros: 15–30 s también vale
timeout: timeout por comprobación
Tiempo máximo de una comprobación. Si el comando se cuelga, Docker espera hasta timeout y abandona.
Muy corto provoca falsos positivos; muy largo retrasa la detección de fallos. 5–10 s suele ser seguro.
retries: reintentos tras fallo
Cuántos fallos consecutivos hacen falta para marcar unhealthy.
Es un mecanismo anti-ruido: un jitter de red o una carga puntual pueden fallar una comprobación; retries hace el sistema más robusto.
3–5 es razonable. retries=1 es demasiado sensible; retries=10, demasiado lento.
start_period: periodo de gracia al arranque (el más ignorado)
Es el parámetro que más se pasa por alto y el que más dolores de cabeza causa.
Los fallos dentro de start_period no cuentan para retries. En otras palabras, das al servicio un “colchón” de arranque.
¿Por qué importa? Las bases de datos tardan en arrancar. PostgreSQL inicializa el directorio de datos; MySQL carga índices. Sin start_period, el healthcheck empieza a fallar en el segundo 2 y el servicio puede quedar unhealthy antes de agotar retries.
Valores recomendados:
- PostgreSQL/MySQL: 30–60 s
- Servicios ligeros (Redis): 15–30 s
- Con scripts de inicialización pesados: hasta 120 s
Yo suelo poner 60 s: prefiero esperar un poco más a tener falsos unhealthy.
Errores frecuentes y cómo evitarlos
Error 1: referencia incorrecta a variables de entorno
# ❌ Incorrecto: Compose interpola antes del arranque y toma variables del host
test: ["CMD", "mysqladmin", "ping", "-p$MYSQL_ROOT_PASSWORD"]
# ✅ Correcto: escapa con $$ para que el shell del contenedor lo resuelva
test: ["CMD-SHELL", "mysqladmin ping -p$$MYSQL_ROOT_PASSWORD"]
Error 2: start_period demasiado corto
# ❌ La base de datos aún no terminó de inicializar y ya empieza a contar fallos
healthcheck:
test: ["CMD", "pg_isready"]
interval: 5s
retries: 3
start_period: 10s # ¡Demasiado corto!
# ✅ Da tiempo suficiente de arranque
healthcheck:
start_period: 60s # Mucho más tranquilo
Error 3: la herramienta de comprobación no existe
Este error es especialmente traicionero porque Docker falla en silencio.
# ❌ Si la imagen no tiene curl, el healthcheck fallará siempre
test: ["CMD", "curl", "-f", "http://localhost/health"]
# ✅ Asegura que la herramienta exista, o usa la que trae la imagen
test: ["CMD", "wget", "--spider", "http://localhost/health"] # Alpine incluye wget
Con el healthcheck listo, veamos bases de datos concretas.
Healthcheck de PostgreSQL en la práctica
La imagen oficial de PostgreSQL trae una herramienta muy útil: pg_isready.
Está pensada para comprobar si PostgreSQL está ready; es más fiable que escribir una consulta SQL a mano.
Configuración básica (recomendada)
version: '3.8'
services:
web:
image: node:20-alpine
depends_on:
db:
condition: service_healthy # Clave: esperar al healthcheck
restart: true # Si la base de datos reinicia, la app también
environment:
DATABASE_URL: postgresql://postgres:password@db:5432/myapp
command: npm start
db:
image: postgres:16
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: myapp
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d myapp"]
interval: 10s
timeout: 5s
retries: 3
start_period: 60s
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Detalle del comando pg_isready
pg_isready -U postgres -d myapp
-U: nombre de usuario; debe existir-d: nombre de base de datos (opcional, pero recomendable)
¿Por qué -U? Sin él, pg_isready intenta el usuario del sistema y los logs se llenan de warnings. No rompe nada, pero molesta.
Configuración avanzada: añadir una consulta real
pg_isready solo comprueba si el puerto responde, no si la base puede ejecutar consultas. Si necesitas algo más estricto:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres && psql -U postgres -d myapp -c 'SELECT 1'"]
interval: 10s
timeout: 10s # Aumenta timeout por la consulta extra
retries: 3
start_period: 60s
SELECT 1 es la consulta más simple; si funciona, la base no solo arrancó, sino que procesa SQL.
En la mayoría de casos basta pg_isready básico.
Efecto en el arranque real
Tras configurarlo, arranca y observa:
$ docker-compose up
Creating network "myapp_default" ... done
Creating myapp_db_1 ... done
Waiting for myapp_db_1 to be healthy... ← Fíjate en esta línea
Creating myapp_web_1 ... done
db_1 | PostgreSQL init process complete; ready for start up.
db_1 | database system is ready to accept connections
web_1 | Server listening on port 3000 ← La app arranca cuando la base está ready
Verás una pausa clara: Docker espera a que db pase a healthy. Puede tardar 30–60 s, pero a cambio obtienes arranques sin fallos.
Diagnóstico: el contenedor sigue unhealthy
Si la base de datos permanece unhealthy, revisa los logs del healthcheck:
# Ver estado de salud del contenedor
$ docker inspect --format='{{json .State.Health}}' myapp_db_1 | jq
{
"Status": "unhealthy",
"FailingStreak": 5,
"Log": [
{
"Start": "2024-12-17T03:15:30Z",
"End": "2024-12-17T03:15:30Z",
"ExitCode": 1,
"Output": "pg_isready: could not connect to server: Connection refused"
}
]
}
Causas habituales:
- start_period demasiado corto: la base aún inicializa cuando empiezan a contarse fallos
- Usuario o base de datos incorrectos: pg_isready no puede conectar
- PostgreSQL no arrancó bien: revisa
docker logs myapp_db_1
Healthcheck de MySQL en la práctica
En MySQL el healthcheck suele usar mysqladmin ping, herramienta de administración incluida.
Configuración básica (recomendada)
version: '3.8'
services:
web:
image: node:20-alpine
depends_on:
db:
condition: service_healthy
restart: true
environment:
DATABASE_URL: mysql://root:password@db:3306/myapp
command: npm start
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: myapp
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-ppassword"]
interval: 10s
timeout: 5s
retries: 3
start_period: 60s
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
Detalle del comando mysqladmin ping
mysqladmin ping -h localhost -u root -ppassword
-h: host (dentro del contenedor, localhost)-u: usuario-p: contraseña (sin espacio entre-py la contraseña)
Si MySQL está bien, devuelve:
mysqld is alive
Código de salida 0: healthcheck superado.
Forma correcta de manejar la contraseña
Opción 1: contraseña directa (entorno de desarrollo)
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-ppassword"]
Directo, pero la contraseña queda en el archivo.
Opción 2: variable de entorno (recomendado)
db:
environment:
MYSQL_ROOT_PASSWORD: password
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"]
# Nota: usa $$ en lugar de $
Aquí hay una trampa: debes usar $$, no $.
¿Por qué? Docker Compose interpola variables antes del arranque. Con $MYSQL_ROOT_PASSWORD, Compose busca la variable en el host, no en el contenedor. $$ le dice a Compose: “no lo toques; que el shell del contenedor lo resuelva”.
Opción 3: comprobación sin contraseña (la más simple, pero discutible)
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
Algunas configuraciones de MySQL permiten conexión local sin contraseña. En producción no lo recomiendo.
Consideraciones especiales de MySQL 8.0
MySQL 8.0 usa por defecto el plugin caching_sha2_password, lo que puede impedir conexiones de clientes antiguos. Si tu app falla por autenticación, puedes forzar el plugin clásico:
db:
image: mysql:8.0
command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: myapp
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-ppassword"]
interval: 10s
timeout: 5s
retries: 3
start_period: 60s
Problemas frecuentes
Problema 1: Access denied for user ‘root’@‘localhost’
Contraseña incorrecta o variable de entorno que no se aplicó. Comprueba:
- Ortografía de MYSQL_ROOT_PASSWORD
- Que la contraseña del healthcheck coincida
- Que hayas usado
$$para escapar
Problema 2: el contenedor tarda mucho y permanece en starting
MySQL tarda en inicializar el directorio de datos, sobre todo en el primer arranque. Asegura un start_period amplio; 60 s suele bastar. Con scripts init que importan muchos datos, puede hacer falta 120 s o más.
Problema 3: el healthcheck pasa, pero la app no conecta
Puede ser red o configuración de la app. Comprueba:
- Cadena de conexión correcta (host
db, nolocalhost) - Configuración de red Docker
docker network inspectpara ver si los contenedores comparten red
Healthcheck de otras bases de datos y servicios
Con PostgreSQL y MySQL dominados, el resto es similar. Aquí va una referencia rápida.
Redis
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
start_period: 15s
redis-cli ping devuelve PONG con código 0. Redis arranca rápido; start_period puede ser corto.
MongoDB
mongo:
image: mongo:7
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
Nota: desde MongoDB 6.0 se usa mongosh en lugar del antiguo mongo. En versiones anteriores:
test: ["CMD", "mongo", "--eval", "db.adminCommand('ping')"]
RabbitMQ
rabbitmq:
image: rabbitmq:3-management-alpine
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "ping"]
interval: 10s
timeout: 5s
retries: 3
start_period: 40s
RabbitMQ arranca lento; start_period de 40 s o más.
Servicio HTTP genérico
Si el servicio expone /health o /ping, puedes usar wget o curl:
api:
image: myapp:latest
healthcheck:
test: ["CMD", "wget", "--spider", "--quiet", "http://localhost:8080/health"]
# O con curl (si la imagen lo incluye)
# test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 10s
timeout: 3s
retries: 3
start_period: 20s
--spider hace que wget solo compruebe sin descargar; --quiet suprime logs.
Nota: asegura que la imagen incluya wget o curl. Alpine trae wget; Debian/Ubuntu suelen traer curl.
Servicios sin herramienta dedicada
Si no hay utilidad de healthcheck, netcat (nc) puede comprobar el puerto:
service:
image: some-service:latest
healthcheck:
test: ["CMD-SHELL", "nc -z localhost 9000 || exit 1"]
interval: 10s
timeout: 3s
retries: 3
start_period: 30s
nc -z solo comprueba si el puerto está abierto, sin conexión real. Es más tosco y no garantiza que el servicio esté ready.
wait-for-it: ¿sigue haciendo falta?
Si buscas orden de arranque en Docker, verás muchos artículos recomendando wait-for-it o scripts similares.
La idea: añadir lógica de espera en el entrypoint de la app y comprobar si el puerto TCP del servicio dependiente responde.
Enfoque clásico con wait-for-it
web:
image: node:20-alpine
depends_on:
- db # depends_on simple, sin comprobar salud
volumes:
- ./wait-for-it.sh:/wait-for-it.sh # Montar script
command: ["/wait-for-it.sh", "db:5432", "--", "npm", "start"]
wait-for-it.sh comprueba db:5432 en bucle hasta que responde y entonces ejecuta npm start.
¿Por qué ya no se recomienda?
La mejor práctica en 2024 es: si puedes usar healthcheck nativo, no uses scripts.
Razones:
- Configuración más clara: la lógica de salud vive en el servicio de base de datos; las dependencias se ven de un vistazo
- Sin archivos extra: no mantienes scripts ni montas volumes
- Más potente: healthcheck comprueba disponibilidad real; TCP solo dice que el puerto responde
- Mejor reutilización: configuras healthcheck una vez y todos los servicios dependientes se benefician
Hay un artículo muy citado: “Forget wait-for-it, use docker-compose healthcheck and depends_on instead”.
¿Cuándo wait-for-it sigue teniendo sentido?
Dos casos especiales:
Caso 1: no puedes modificar la imagen ni el compose
Por ejemplo, imagen de terceros sin healthcheck y sin permiso para añadirlo. Entonces wait-for-it en el lado de la app puede ser la única opción.
Caso 2: esperar varios servicios
./wait-for-it.sh db:5432 redis:6379 rabbitmq:5672 -- npm start
depends_on también admite varios servicios, pero wait-for-it puede ser más conciso en la línea de comando. No es un escenario muy frecuente.
Otras alternativas
Además de wait-for-it:
- dockerize: escrito en Go, más funciones, soporta plantillas de variables de entorno
- wait-for: versión simplificada en shell puro
- docker-compose-wait: en Python, soporta comprobaciones HTTP
Pero en 2024, si puedes usar healthcheck, evita complicarte.
Lista de diagnóstico y buenas prácticas
¿Configurado y sigue fallando? Sigue esta lista; el 90 % de los casos se resuelven.
Comandos de diagnóstico rápido
# 1. Ver estado de todos los contenedores
$ docker-compose ps
NAME COMMAND SERVICE STATUS PORTS
myapp_db postgres db healthy 5432/tcp
myapp_web npm start web running 0.0.0.0:3000->3000/tcp
# 2. Ver detalle del healthcheck
$ docker inspect --format='{{json .State.Health}}' myapp_db_1 | jq
# 3. Ver logs de contenedores
$ docker-compose logs db
$ docker-compose logs web
# 4. Seguir logs en tiempo real
$ docker-compose logs -f --tail=100
Árbol de decisión para problemas frecuentes
Problema: el contenedor permanece en starting y nunca pasa a healthy
- ¿start_period demasiado corto? → prueba 60 s
- ¿Comando de healthcheck correcto? →
docker inspectpara ver el comando real - Ejecuta el comando manualmente →
docker exec -it myapp_db_1 pg_isready -U postgres
Problema: pasa a unhealthy y vuelve a healthy, en bucle
- interval demasiado corto o recursos justos → prueba 10 s o 15 s
- retries demasiado bajo → prueba 5
- La base de datos tiene problemas de rendimiento → revisa sus logs
Problema: healthcheck OK, pero la app no conecta
- ¿Cadena de conexión correcta? → host = nombre del servicio (
db), nolocalhost - ¿Puertos correctos? → entre contenedores usa el puerto interno (5432), no el mapeado al host
- ¿Misma red? → confirma que todos los servicios comparten network
Buenas prácticas en producción
1. Valores recomendados (configuración conservadora)
healthcheck:
interval: 10s # Equilibrio entre respuesta y consumo
timeout: 5s # Tiempo suficiente para el comando
retries: 5 # Tolera fallos puntuales
start_period: 60s # Tiempo amplio para arranque de base de datos
Estos valores son estables en la mayoría de escenarios. Con scripts init pesados, start_period puede subir a 120 s.
2. Usar estrategia restart
web:
depends_on:
db:
condition: service_healthy
restart: true # Si la base reinicia, la app también
restart: unless-stopped # Reinicio automático al salir
restart: true garantiza que, si la base se actualiza o reinicia, los servicios dependientes reinician y reconectan.
3. Límites de recursos
El healthcheck consume recursos, aunque pocos. Si el sistema va justo:
healthcheck:
interval: 30s # Alarga el intervalo
timeout: 3s # Acorta timeout
En la práctica, el overhead del healthcheck suele ser despreciable salvo con cientos de contenedores.
4. Monitorizar el estado de salud
En producción conviene seguir el healthcheck con herramientas de monitorización. Los eventos de Docker pueden ir a Prometheus, Grafana, etc.
# docker-compose.yml
services:
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 3
start_period: 60s
labels:
- "prometheus.io/scrape=true" # Para que Prometheus recoja el estado
5. Configuración multi-entorno
Desarrollo y producción pueden diferir:
# docker-compose.yml (desarrollo)
db:
healthcheck:
start_period: 30s # Menos datos, arranque más rápido
# docker-compose.prod.yml (producción)
db:
healthcheck:
start_period: 120s # Más datos, arranque más lento
interval: 5s # Comprobaciones más frecuentes
Al arrancar, indica los archivos:
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up
Trucos de depuración
Truco 1: probar el comando de healthcheck a mano
Entra al contenedor y ejecuta el comando para ver qué falla:
$ docker exec -it myapp_db_1 sh
/# pg_isready -U postgres -d myapp
/var/run/postgresql:5432 - accepting connections
/# echo $?
0 ← Código 0 = éxito
Truco 2: desactivar temporalmente el healthcheck
Al depurar, comenta el healthcheck y usa depends_on simple para descartar que el problema sea el propio healthcheck:
web:
depends_on:
- db # Modo simple temporal
# db:
# condition: service_healthy
Cuando la app conecte bien, vuelve a activar el healthcheck.
Truco 3: ver eventos de Docker
Docker registra todos los eventos de contenedor, incluidos cambios de healthcheck:
$ docker events --filter 'event=health_status'
2024-12-17T03:15:30.123456789Z container health_status: healthy (name=myapp_db_1)
2024-12-17T03:16:45.987654321Z container health_status: unhealthy (name=myapp_db_1)
Te ayuda a ver cuándo pasó a unhealthy y cruzarlo con los logs.
Conclusión
Volvamos al problema del inicio: ¿por qué depends_on no basta?
La respuesta en tres palabras: no es suficiente.
depends_on por defecto solo ordena el arranque de contenedores, no la disponibilidad del servicio. Que el contenedor de la base de datos esté running no significa que acepte conexiones; ese gap es la raíz de la mayoría de incidentes.
La solución es directa:
- Añade healthcheck a la base de datos con pg_isready o mysqladmin ping
- Usa condition: service_healthy para que la app espere al healthcheck
- Configura un start_period razonable (desde 60 s) para dar tiempo de inicialización
Con estos tres pasos, los fallos de arranque suelen desaparecer.
Acciones rápidas:
- Ahora mismo: copia la configuración de PostgreSQL o MySQL de este artículo a tu proyecto y ajusta variables de entorno
- Esta noche: actualiza los depends_on del equipo a service_healthy
- Próxima reunión: comparte con el equipo para unificar el estándar
Si te encuentras un caso que no cubrimos, déjalo en los comentarios. Docker Compose tiene más trampas; podemos ir rellenándolas juntos.
Por último, si este artículo te ayudó con un problema de arranque que llevabas tiempo arrastrando, un like me sirve de feedback. Esos momentos de “por fin funciona” son justo lo que me motiva a escribir.
Flujo completo de configuración de healthcheck en Docker Compose
Soluciona fallos de arranque cuando la base de datos aún no está lista, con plantillas completas para PostgreSQL y MySQL
⏱️ Estimated time: 30 min
- 1
Step 1: Entender la causa: las limitaciones de depends_on
Causa raíz:
• depends_on de Docker solo controla el orden de arranque de contenedores, no si el servicio está realmente listo
• Compose solo espera a que el contenedor esté en ejecución, no a que el servicio sea usable
• Documentación oficial de Docker: Compose does not wait until a container is "ready", only until it's running
• Traducción: Compose solo espera a que el contenedor arranque, no a que el servicio sea usable
Diferencia entre arranque del contenedor y servicio listo:
• 0 s: Docker levanta el contenedor, arranca el proceso postgres (depends_on da luz verde aquí)
• 2 s: inicializa el directorio de datos
• 5 s: carga la configuración
• 8 s: ejecuta scripts init (si los hay)
• 12 s: por fin ready, acepta conexiones
Entre medias hay un gap de unos 12 segundos; si tu app web intenta conectar en el segundo 1, el resultado será Connection refused. - 2
Step 2: Las tres condition de depends_on
Tres condition de depends_on:
1. service_started (por defecto)
• Solo espera el arranque del contenedor, no la disponibilidad del servicio
• No recomendado
2. service_healthy (recomendado)
• Espera arranque del contenedor y healthcheck superado
• Garantiza que el servicio esté realmente listo
3. service_completed_successfully
• Espera a que el contenedor termine con éxito
• Ideal para tareas puntuales
Recomendación: service_healthy
• En docker-compose.yml: depends_on: db: condition: service_healthy
• Garantiza que la base de datos esté lista antes de arrancar la aplicación - 3
Step 3: healthcheck correcto para PostgreSQL y MySQL
Healthcheck de PostgreSQL:
• Usa pg_isready para comprobar si la base de datos está lista
• Ejemplo:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
start_period: 60s (tiempo suficiente para inicializar)
Healthcheck de MySQL:
• Usa mysqladmin ping para comprobar si la base de datos está lista
• Ejemplo:
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$MYSQL_ROOT_PASSWORD"]
interval: 5s
timeout: 5s
retries: 5
start_period: 60s
Configuración completa:
• Define healthcheck en docker-compose.yml (comando, intervalo, timeout, reintentos)
• Usa condition: service_healthy en depends_on
• Garantiza que la base de datos esté lista antes de arrancar la aplicación - 4
Step 4: Solución de problemas y buenas prácticas
Lista de diagnóstico:
1. Comprobar que el comando de healthcheck es correcto
• Ejecútalo manualmente dentro del contenedor y confirma que devuelve 0
• Ejemplo: docker exec -it db_container pg_isready -U postgres
2. Desactivar temporalmente el healthcheck
• Al depurar, comenta el healthcheck
• Usa depends_on simple para descartar problemas del propio healthcheck
3. Revisar eventos de Docker
• Usa docker events para ver arranque y cambios de healthcheck
Buenas prácticas:
1. Añade healthcheck a la base de datos con pg_isready o mysqladmin ping
2. Usa condition: service_healthy para que la app espere al healthcheck
3. Configura un start_period razonable (desde 60 s) para dar tiempo de inicialización
Con estos tres pasos, los fallos de arranque de contenedores suelen desaparecer.
Alternativa moderna a wait-for-it:
• Combina depends_on + healthcheck
• Sin scripts extra, configuración simple y fiable
• Válido para cualquier proyecto Docker Compose
FAQ
¿Por qué depends_on no basta? ¿Qué diferencia hay entre arranque del contenedor y servicio listo?
En la documentación oficial hay una frase clave que muchos pasan por alto: Compose does not wait until a container is "ready", only until it's running. Traducción: Compose solo espera a que el contenedor arranque, no a que el servicio sea usable.
Diferencia temporal: imagina el arranque de un contenedor PostgreSQL:
• 0 s: Docker levanta el contenedor, arranca postgres (depends_on da luz verde aquí)
• 2 s: inicializa el directorio de datos
• 5 s: carga la configuración
• 8 s: ejecuta scripts init (si los hay)
• 12 s: por fin ready, acepta conexiones
Entre medias hay un gap de unos 12 segundos; si tu app web intenta conectar en el segundo 1, el resultado será Connection refused.
¿Cuáles son las tres condition de depends_on?
1) service_started (por defecto):
• Solo espera el arranque del contenedor, no la disponibilidad del servicio
• No recomendado
2) service_healthy (recomendado):
• Espera arranque del contenedor y healthcheck superado
• Garantiza que el servicio esté realmente listo
• En docker-compose.yml: depends_on: db: condition: service_healthy
3) service_completed_successfully:
• Espera a que el contenedor termine con éxito
• Ideal para tareas puntuales
Recomendación: service_healthy para que la app arranque solo cuando la base de datos esté lista.
¿Cómo configurar healthcheck para PostgreSQL y MySQL?
• Usa pg_isready para comprobar si la base de datos está lista
• Ejemplo:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
start_period: 60s (tiempo suficiente para inicializar)
Healthcheck de MySQL:
• Usa mysqladmin ping para comprobar si la base de datos está lista
• Ejemplo:
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$MYSQL_ROOT_PASSWORD"]
interval: 5s
timeout: 5s
retries: 5
start_period: 60s
Configuración completa: define healthcheck en docker-compose.yml (comando, intervalo, timeout, reintentos) y usa condition: service_healthy en depends_on para que la base de datos esté lista antes de arrancar la aplicación.
¿Cómo diagnosticar problemas de healthcheck?
1) Comprobar que el comando de healthcheck es correcto:
• Ejecútalo manualmente dentro del contenedor y confirma que devuelve 0
• Ejemplo: docker exec -it db_container pg_isready -U postgres
• Código de salida 0 indica éxito
2) Desactivar temporalmente el healthcheck:
• Al depurar, comenta el healthcheck
• Usa depends_on simple para descartar problemas del propio healthcheck
• Cuando la app conecte bien, vuelve a añadir el healthcheck
3) Revisar eventos de Docker:
• Usa docker events para ver arranque y cambios de healthcheck
¿Cuáles son las buenas prácticas de healthcheck?
1) Añade healthcheck a la base de datos con pg_isready o mysqladmin ping
2) Usa condition: service_healthy para que la app espere al healthcheck
3) Configura un start_period razonable (desde 60 s) para dar tiempo de inicialización
Con estos tres pasos, los fallos de arranque de contenedores suelen desaparecer.
Alternativa moderna a wait-for-it:
• Combina depends_on + healthcheck
• Sin scripts extra, configuración simple y fiable
• Válido para cualquier proyecto Docker Compose
Acciones rápidas:
• Ahora mismo: copia la configuración de PostgreSQL o MySQL de este artículo a tu proyecto y ajusta variables de entorno
• Esta noche: actualiza los depends_on del equipo a service_healthy
• Próxima reunión: comparte con el equipo para unificar el estándar
16 min de lectura · Publicado el: 17 dic 2025 · Actualizado el: 21 ago 2026
Guía práctica de Docker
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
Orquestación multi-servicio con Docker Compose: entorno local en un solo comando
Orquesta Web, API, MySQL y Redis con Docker Compose y arranca tu entorno local con un comando. Olvídate de instalaciones manuales, conflictos de versiones y puertos ocupados: un nuevo miembro del equipo puede clonar el repo y empezar en 5 minutos; cambiar de proyecto lleva segundos.
Parte 8 de 38
Siguiente
Guía de solución de errores en Docker Compose: 5 fallos frecuentes y cómo resolverlos rápido
¿Errores al ejecutar Docker Compose? Este artículo recopila 5 tipos habituales — conflictos de puertos, problemas de red, fallos de build, salida del contenedor y errores de permisos — con un flujo de diagnóstico sistemático para localizar el problema en 5 minutos.
Parte 10 de 38



Comentarios
Inicia sesión con GitHub para dejar un comentario