docker-compose-depends-on-healthchecks

Dependencias y orden de arranque en Docker Compose

  • 4 min

Los Healthchecks en Docker son pruebas de diagnóstico que permiten al motor verificar si un contenedor está encendido y si la aplicación que corre está lista .

Imagina que has escrito tu docker-compose.yml. Tienes tu Backend y tu base de datos. Ejecutas docker compose up y… tu Backend se cierra con error.

Miras los logs y ves:

Connection Refused: Cannot connect to database

¿Cómo puede ser? Pero si tu has puesto depends_on. La base de datos tenía que haber estado disponible… evidentemente no (si no no haría este artículo).

Hoy vamos a destapar por qué depends_on no es suficiente por sí solo, y aprenderemos a usar la herramienta para crear arranques ordenados: los Healthchecks 👇.

La mentira de depends_on

Al hablar de Docker Compose vimos la instrucción depends_on, que sirve para iniciar unos contenedores después de otros.

depends_on solo espera a que el contenedor empiece, no a que esté listo.

El problema es que encender un contenedor no significa que el software de su interior esté listo.

Cuando escribimos esto en nuestro archivo YAML:

services:
  web:
    image: mi-app
    depends_on:
      - db
  db:
    image: postgres
Copied!

Le decimos a Docker que arranque el contenedor db primero. En cuanto el proceso haya arrancado, arranque web.

Pero una base de datos puede tardar 10 o 20 segundos en iniciar realmente. Tiene que cargar archivos, verificar integridad, abrir sockets…

Durante esos segundos, el contenedor está “corriendo” (Up), pero no está listo para recibir conexiones.

Tu aplicación web, que es mucho más rápida arrancando. Digamos, un 1 segundo. Intenta conectar, la base de datos la rechaza (porque sigue cargando) y tu aplicación muere.

La solución Healthchecks

Para arreglar esto, necesitamos que Docker sepa diferenciar entre “estoy encendido” y “estoy listo para trabajar”.

Esto se hace definiendo un Healthcheck. Básicamente un comando que Docker ejecutará repetidamente dentro del contenedor para preguntar: ¿Estás listo?.

Paso 1️⃣: Definir el Healthcheck en la DB

Vamos a añadir la sección healthcheck a nuestro servicio de base de datos. El comando dependerá del software (Postgres, MySQL, Redis…).

Supongamos un ejemplo con PostgreSQL:

services:
  db:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secreto
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
Copied!

¿Qué significa esto?

  1. test: Ejecuta el comando pg_isready (propio de Postgres) dentro del contenedor.
  2. interval: Hazlo cada 5 segundos.
  3. retries: Si falla 5 veces seguidas, marca el contenedor como unhealthy (enfermo).

Ahora, si haces docker ps, verás en la columna STATUS algo nuevo: Up 10 seconds (health: starting) y luego (healthy).

Paso 2️⃣: La espera condicional

Ahora tenemos que decirle a nuestra aplicación web que no arranque solo cuando la DB empiece, sino cuando la DB esté lista.

Cambiamos la sintaxis corta de depends_on por la sintaxis larga:

services:
  web:
    image: mi-app
    depends_on:
      db:
        condition: service_healthy  # <--- ¡Lo nuevo!
Copied!

Con esto, Docker Compose hará lo siguiente:

  1. Arranca db.
  2. Espera. Ejecuta el healthcheck… falla… espera…
  3. Ejecuta el healthcheck… ¡Éxito! El estado pasa a healthy.
  4. Arranca web.

Ejemplo completo de Stack robusto

Aquí tienes un docker-compose.yml a prueba de balas que podrías usar en producción:

version: '3.8'

services:
  # Base de Datos
  database:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: app_db
    volumes:
      - db_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 10s
      retries: 5

  # Aplicación Web
  webapp:
    image: mi-imagen-web:latest
    ports:
      - "80:80"
    environment:
      DB_HOST: database
    depends_on:
      database:
        condition: service_healthy
    restart: on-failure

volumes:
  db_data:
Copied!