Un contenedor que aparece como «Up» no siempre está listo para atender peticiones: puede haber arrancado el proceso pero seguir cargando la base de datos. Ahí está la diferencia entre estar en ejecución y estar sano. En esta guía verás cómo definir un healthcheck en Docker para que Compose sepa cuándo un servicio funciona de verdad, qué política de reinicio elegir para que un contenedor caído se recupere solo, y cómo encadenar servicios con depends_on para que tu aplicación no arranque antes que su base de datos. La misma guía está disponible en inglés.

Puntos clave

  • Un healthcheck es un comando que Docker ejecuta dentro del contenedor cada cierto tiempo; según su código de salida (0 sano, 1 enfermo) el estado pasa de starting a healthy o unhealthy.
  • Los valores por defecto, heredados de la instrucción HEALTHCHECK del Dockerfile, son interval: 30s, timeout: 30s, retries: 3 y start_period: 0s.
  • Hay cuatro políticas de reinicio: no (por defecto), always, on-failure y unless-stopped; la diferencia clave está en qué ocurre tras reiniciar el demonio de Docker.
  • depends_on admite tres condiciones: service_started, service_healthy y service_completed_successfully. Solo service_healthy espera a que el healthcheck dé el visto bueno.
  • on-failure acepta un límite de reintentos (on-failure:5), útil para no entrar en un bucle infinito de reinicios.
  • El atributo start_interval (comprobaciones más frecuentes durante el arranque) requiere Docker Compose v2.20.2 y Docker Engine 25.0 o superior.

¿Qué es un healthcheck en Docker y por qué importa?

Cuando ejecutas docker compose ps, un contenedor puede aparecer como Up en cuanto su proceso principal arranca. Pero «arrancado» no significa «listo»: una base de datos tarda unos segundos en aceptar conexiones, y un servidor web puede estar levantando el intérprete antes de responder. La documentación de Docker lo resume sin rodeos: «En el arranque, Compose no espera a que un contenedor esté "listo", solo a que esté en ejecución». Esa brecha es la causa de la mitad de los errores de arranque en un docker-compose.yml con varios servicios.

Un healthcheck cierra esa brecha. Es un comando que Docker ejecuta de forma periódica dentro del contenedor; si termina con código de salida 0, el contenedor se considera sano, y si termina con 1, enfermo. Mientras se cumple el start_period inicial, los fallos no cuentan; después, tras varios fallos seguidos (retries), Docker marca el contenedor como unhealthy. Ese estado es visible en docker compose ps y, sobre todo, otros servicios pueden esperarlo. Sin un motor instalado no hay nada que comprobar; si empiezas de cero, repasa primero cómo instalar Docker en Ubuntu 24.04.

¿Cómo se define un healthcheck en Compose?

El healthcheck se declara dentro del servicio, con seis campos posibles. El único obligatorio es test, el comando que decide la salud:

services:
  web:
    image: nginx:1.27.2
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 20s
      start_interval: 5s

El campo test admite tres formas. ["CMD", "orden", "arg"] ejecuta el binario directamente, sin shell. ["CMD-SHELL", "orden con pipes"] lo pasa por el shell del contenedor, necesario si usas tuberías o variables. Y ["NONE"] desactiva cualquier healthcheck heredado de la imagen. Una cadena suelta equivale a CMD-SHELL.

Los demás campos ajustan el ritmo. interval es el tiempo entre comprobaciones; timeout, lo que Docker espera antes de dar una comprobación por fallida; retries, cuántos fallos consecutivos hacen falta para marcar unhealthy; y start_period, una ventana de gracia al arrancar durante la cual los fallos no penalizan. Como recuerda la documentación, el healthcheck «funciona de la misma forma, y con los mismos valores por defecto, que la instrucción HEALTHCHECK del Dockerfile»: interval y timeout valen 30 segundos, retries es 3 y start_period es 0. El campo start_interval permite comprobar cada pocos segundos durante el arranque, para detectar antes que el servicio ya está sano, pero exige Docker Engine 25.0 o superior.

Políticas de reinicio: no, always, on-failure y unless-stopped

El healthcheck informa; la política de reinicio actúa. Se declara con la clave restart y determina qué hace Docker cuando el contenedor se detiene. Hay cuatro valores:

  • no: el valor por defecto. El contenedor no se reinicia bajo ninguna circunstancia.
  • always: se reinicia siempre que se detenga y también cuando arranca el demonio de Docker, hasta que lo elimines.
  • on-failure: se reinicia solo si el contenedor sale con un código distinto de cero (un error). Acepta un tope opcional de intentos, on-failure:5.
  • unless-stopped: como always, pero si tú detienes el contenedor a mano, no vuelve a arrancar tras reiniciar el demonio.

La diferencia entre always y unless-stopped es sutil pero importante: tras un docker stop manual seguido de un reinicio del servidor, always vuelve a levantar el contenedor mientras que unless-stopped respeta tu decisión de dejarlo parado. Para servicios de un homelab que quieres que sobrevivan a un reinicio del host, unless-stopped suele ser la opción más segura; es la que usamos en todos los ejemplos de esta serie. Ten en cuenta un matiz: la documentación avisa de que «las políticas de reinicio solo se aplican a contenedores», así que bajo Docker Swarm se ignora restart y se usa la configuración de reinicio del servicio.

depends_on con la condición service_healthy

Aquí es donde el healthcheck deja de ser informativo y se vuelve accionable. Por defecto, depends_on solo garantiza el orden de arranque, no que la dependencia esté lista. La forma larga de depends_on acepta tres condiciones:

  1. service_started: espera únicamente a que el contenedor esté en ejecución (el comportamiento clásico).
  2. service_healthy: espera a que la dependencia esté healthy según su healthcheck.
  3. service_completed_successfully: espera a que la dependencia termine con código 0, útil para contenedores de inicialización o migraciones.

Con service_healthy, Compose no arranca tu aplicación hasta que la base de datos ha pasado su healthcheck. Es la pieza que elimina de raíz los errores de «connection refused» al levantar la pila. Antes de encadenar servicios conviene tener clara la configuración que reciben: repasa las variables de entorno y secretos en Docker para pasar contraseñas de forma segura, y si quieres activar o desactivar servicios enteros según el entorno, mira los perfiles de Docker Compose.

Ejemplo completo con base de datos y app

Este docker-compose.yml reúne todo: una base de datos PostgreSQL con su healthcheck, una aplicación que espera a que esté sana y una política de reinicio en ambos servicios. Es copia y pega directa:

services:
  db:
    image: postgres:16.4
    restart: unless-stopped
    environment:
      POSTGRES_DB: miapp
      POSTGRES_USER: miapp
      POSTGRES_PASSWORD: cambia_esta_clave
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U miapp -d miapp"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  app:
    image: nginx:1.27.2
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "8080:80"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 20s

volumes:
  db_data:

Levanta la pila y observa cómo la app espera a la base de datos antes de arrancar:

docker compose up -d
docker compose ps
docker inspect --format '{{.State.Health.Status}}' $(docker compose ps -q db)

La columna STATUS de docker compose ps mostrará (healthy) junto al contenedor db en cuanto pg_isready responda, y solo entonces verás arrancar app. El último comando imprime el estado de salud crudo (starting, healthy o unhealthy) para depurar. Si necesitas afinar la propia base de datos, la guía de cómo instalar PostgreSQL con Docker entra en el detalle del healthcheck con pg_isready y de las copias de seguridad.

Un aviso habitual: nunca fijes la imagen a :latest en producción. No es reproducible y una actualización silenciosa puede cambiar el binario del healthcheck (por ejemplo, si curl deja de venir en la imagen). Fija una versión concreta como postgres:16.4 y reserva :latest solo como ejemplo de lo que no debes hacer.

Preguntas frecuentes

¿Qué diferencia hay entre depends_on y un healthcheck?

depends_on controla el orden en que Compose arranca los servicios; el healthcheck controla si un servicio está sano. Por separado, depends_on sin condición solo espera a que el contenedor esté en ejecución. La combinación depends_on con condition: service_healthy es la que realmente espera a que la dependencia pase su healthcheck.

¿Cuál es la diferencia entre always y unless-stopped?

Ambas reinician el contenedor cuando falla. La diferencia aparece tras un reinicio del demonio de Docker: always vuelve a levantar el contenedor aunque tú lo hubieras parado a mano, mientras que unless-stopped respeta esa parada manual y lo deja detenido. Para servicios de un homelab, unless-stopped suele ser lo más predecible.

¿Por qué mi contenedor se queda en starting y nunca pasa a healthy?

Casi siempre es el comando test. Comprueba que el binario existe dentro de la imagen (muchas imágenes mínimas no traen curl ni wget), que el puerto y la ruta son correctos, y que el start_period da tiempo suficiente al servicio a inicializarse. Ejecuta el comando del test a mano con docker compose exec para ver el error real.

Conclusión

Un servicio bien montado se autovigila y se autorrecupera. El healthcheck le dice a Docker cuándo tu aplicación está de verdad lista, la política de reinicio la levanta de nuevo si cae, y depends_on con service_healthy ordena el arranque para que nada se ejecute antes de tiempo. Con esos tres mecanismos, una pila de contenedores sobrevive a reinicios del servidor y a arranques lentos sin intervención manual. El siguiente paso natural es organizar servicios opcionales por entorno: sigue con los perfiles de Docker Compose.

Fuentes

  1. Docker Docs: Services top-level element (healthcheck)
  2. Docker Docs: Start containers automatically (restart policies)
  3. Docker Docs: Control startup and shutdown order (depends_on)
  4. docker/compose Releases (start_interval, v2.20.2)
  5. Docker Hub: PostgreSQL official image (pg_isready)

Ruta: Self-hosting con Docker: de cero a producción