Healthchecks en Docker Compose: cómo evitar que tus servicios arranquen antes de tiempo
Aprende a configurar healthchecks reales en Docker Compose para que tu app no intente conectarse a una base de datos que todavía está arrancando. Guía práctica con ejemplos para PostgreSQL, MySQL, Redis y MongoDB, y el uso correcto de depends_on con condition: service_healthy.
Por Equipo Starbyte
Healthchecks en Docker Compose: cómo evitar que tus servicios arranquen antes de tiempo
Uno de los errores más frecuentes con Docker Compose es asumir que si un contenedor arrancó, el servicio dentro ya está listo. No es así. Tu contenedor de PostgreSQL puede estar "running" mientras el motor de base de datos todavía está inicializando. Si tu app intenta conectarse en ese momento, falla.
Los healthchecks resuelven exactamente eso: le dicen a Docker cuándo un servicio está realmente listo para recibir conexiones.
Qué es un healthcheck y cómo funciona
Un healthcheck es un comando que Docker ejecuta periódicamente dentro del contenedor para verificar si el servicio responde. Según el resultado, Docker marca el contenedor con uno de estos estados:
| Estado | Significado |
|---|---|
starting |
El contenedor arrancó pero aún no pasa el primer chequeo |
healthy |
El comando de healthcheck retorna código 0 |
unhealthy |
El comando falla después de agotar los reintentos |
Anatomía de un healthcheck
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Cada parámetro cumple un rol específico:
- test: el comando que determina si el servicio está sano. Retorna 0 (healthy) o 1 (unhealthy).
- interval: cada cuánto se ejecuta el chequeo.
- timeout: tiempo máximo que puede tardar el comando antes de considerarse fallido.
- retries: cuántas veces puede fallar antes de marcarse como
unhealthy. - start_period: ventana de gracia inicial donde los fallos no cuentan. Fundamental para servicios que tardan en arrancar.
Healthchecks para los servicios más comunes
PostgreSQL
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: miapp
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d miapp"]
interval: 5s
timeout: 5s
retries: 5
start_period: 30s
pg_isready es una utilidad nativa de PostgreSQL diseñada específicamente para verificar si el servidor acepta conexiones.
MySQL
mysql:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD}
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
Redis
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
Redis arranca rápido, así que normalmente no necesita start_period.
MongoDB
mongo:
image: mongo:7
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
La pieza clave: depends_on con condition
Definir healthchecks sin conectarlos a las dependencias no sirve de mucho. La magia está en combinarlos con depends_on usando la forma larga:
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secreto
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
start_period: 30s
cache:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
app:
build: .
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
ports:
- "8000:8000"
Con esta configuración, Docker Compose no arrancará el servicio app hasta que tanto db como cache pasen sus healthchecks. Es la diferencia entre "el contenedor existe" y "el servicio funciona".
Patrón avanzado: migraciones antes de arrancar la app
Si necesitas ejecutar migraciones de base de datos antes de que tu app arranque, puedes usar service_completed_successfully:
services:
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
start_period: 30s
migrate:
build: .
command: npm run migrate
depends_on:
db:
condition: service_healthy
app:
build: .
depends_on:
migrate:
condition: service_completed_successfully
db:
condition: service_healthy
La secuencia queda así: primero arranca db, cuando está healthy arranca migrate, y cuando migrate termina exitosamente arranca app.
Errores comunes
No usar start_period: sin esta ventana de gracia, los primeros chequeos fallidos cuentan como reintentos. Una base de datos que necesita 20 segundos para inicializar se marcará como unhealthy antes de tener oportunidad de arrancar. Siempre configura un start_period generoso para bases de datos.
Chequear solo que el puerto está abierto: un curl al puerto no garantiza que el servicio esté listo. Usa las herramientas nativas de cada servicio (pg_isready, redis-cli ping, mysqladmin ping).
Usar la forma corta de depends_on: escribir depends_on: [db] solo garantiza orden de arranque, no espera al healthcheck. Siempre usa la forma larga con condition: service_healthy.
Olvidar que version ya no es necesario: en Docker Compose V2, el campo version en el YAML está deprecado y se ignora. Puedes eliminarlo de tus archivos.
Cómo depurar healthchecks
Si un servicio se queda en estado unhealthy, estas herramientas te ayudarán:
# Ver el estado de salud de todos los contenedores
docker compose ps
# Inspeccionar el historial de healthchecks de un contenedor
docker inspect --format='{{json .State.Health}}' nombre_contenedor | jq
# Ejecutar manualmente el comando de healthcheck dentro del contenedor
docker compose exec db pg_isready -U postgres
# Ver logs del servicio problemático
docker compose logs db
Requisitos de versión
Los healthchecks con condition: service_healthy requieren Docker Compose V2 versión 2.20.0 o superior. Verifica tu versión con:
docker compose version
Idea clave
Un contenedor en estado "running" no es un servicio listo. Los healthchecks transforman Docker Compose de una herramienta que solo ordena arranques en una que realmente orquesta dependencias. Configurarlos lleva 5 minutos por servicio y elimina la categoría entera de errores de "mi app arrancó antes que la base de datos".