Tutorial

depends_on no basta: healthcheck de PostgreSQL en Compose

Despliegue8 min de lectura8 pasos

Su stack Docker arranca, la base de datos pasa al estado `running` — y su aplicación falla de inmediato con `connection refused` o `FATAL: role does not exist`. La causa es casi siempre la misma: `depends_on` por defecto espera a que el contenedor arranque, no a que el servicio esté operativo. Esta guía le muestra cómo configurar un healthcheck de PostgreSQL fiable en `docker-compose.yml` para eliminar esa race condition de una vez por todas.

Contenido· Por qué `depends_on` falla por defecto1/8
  1. 01Por qué `depends_on` falla por defecto
  2. 02Las consecuencias prácticas de una race condition en el arranque
  3. 03Requisitos previos: Docker Compose v2 y el plugin oficial
  4. 04Configurar un healthcheck de PostgreSQL fiable, paso a paso
  5. 05Comportamiento por defecto frente a healthcheck
  6. 06`pg_isready` o `SELECT 1`: ¿cuál elegir?
  7. 07Adaptar el healthcheck a Redis y MySQL
  8. 08Enlaces internos y próximos pasos

Por qué `depends_on` falla por defecto

Por defecto, depends_on usa la condición service_started. Esto significa que Docker solo espera a que el contenedor de destino esté lanzado — es decir, a que su proceso principal haya arrancado. Eso no dice nada sobre el estado interno del servicio.

PostgreSQL, como la mayoría de las bases de datos, atraviesa varias fases durante la inicialización: la imagen oficial ejecuta scripts de arranque, crea los roles, inicializa las extensiones y prepara el clúster antes de empezar a aceptar conexiones. Esta secuencia puede tardar desde unos segundos hasta más de treinta segundos en un VPS con un disco cargado, un volumen sin preparar o un conjunto grande de extensiones.

Mientras tanto, su aplicación — que sí respeta la directiva depends_on — ya intenta conectarse, y recibe un rechazo seco.

Las consecuencias prácticas de una race condition en el arranque

  • connection refused — el socket TCP de PostgreSQL todavía no está abierto y la aplicación falla en la primera llamada PDO o SQLAlchemy.
  • FATAL: role does not exist — PostgreSQL escucha, pero los scripts de inicialización (docker-entrypoint-initdb.d) todavía no han creado el rol ni la base de datos.
  • FATAL: the database system is starting up — el clúster está recuperándose tras una parada limpia; las conexiones se rechazan temporalmente.
  • Crash loop silencioso — con restart: unless-stopped, Docker relanza la aplicación indefinidamente, los logs se repiten y el problema se toma por un error de la aplicación.
  • Falsos positivos en CI — las pruebas de integración fallan de forma intermitente según la velocidad de arranque del runner.
  • Dependencias en cascada — una API que depende de una aplicación que depende de la base hereda el mismo problema si la cadena depends_on no es correcta de forma uniforme.

Requisitos previos: Docker Compose v2 y el plugin oficial

La condición service_healthy no está disponible en Docker Compose v1 (el binario docker-compose en Python, hoy obsoleto). Se admite desde Docker Compose v2, distribuido como plugin en Go bajo el comando docker compose (sin guion).

Para comprobar su versión:

docker compose version

La salida debe mostrar Docker Compose version v2.x.x o superior. En Debian 12 y Ubuntu 22.04+, el plugin está disponible en los repositorios oficiales de Docker. Si todavía usa docker-compose (v1), migre: el proyecto está archivado y ya no recibe parches de seguridad.

No hace falta ninguna dependencia adicional para PostgreSQL: pg_isready es una herramienta nativa de la imagen oficial postgres, presente en todos los tags desde hace años.

Configurar un healthcheck de PostgreSQL fiable, paso a paso

  1. Entender service_started frente a service_healthy

    depends_on acepta tres condiciones:

    - service_started (por defecto) — espera a que el contenedor simplemente haya arrancado.
    - service_healthy — espera a que el healthcheck del contenedor devuelva healthy.
    - service_completed_successfully — para contenedores de vida corta (jobs, migraciones).

    Para cualquier base de datos, service_healthy es la única condición que garantiza que el servicio acepta conexiones.

  2. Escribir el healthcheck de PostgreSQL en el servicio `db`

    Añada el bloque healthcheck directamente en la definición del servicio 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: 30s

    El campo test recibe una lista: el primer elemento es CMD (Docker ejecuta el comando directamente), seguido de los argumentos. pg_isready devuelve 0 si PostgreSQL está listo para aceptar conexiones con el usuario y la base indicados, y un código distinto de cero en caso contrario — lo que Docker interpreta como healthy o unhealthy.

  3. Entender el papel de `start_period`

    start_period es la ventana de gracia concedida al contenedor para inicializarse antes de que los fallos del healthcheck empiecen a contabilizarse en retries. Durante esa ventana, Docker sí ejecuta el healthcheck, pero un fallo no incrementa el contador.

    Sin start_period, un PostgreSQL que tarda 15 segundos en inicializarse fallaría sus 5 primeras comprobaciones (interval: 5s × 5 intentos = 25 segundos) y pasaría a unhealthy antes incluso de estar operativo.

    El valor recomendado son 30 segundos para un PostgreSQL estándar: lo bastante largo para absorber las inicializaciones lentas (primer arranque con volumen vacío, extensiones pesadas) sin retrasar innecesariamente el arranque en régimen permanente. interval y start_period son distintos: interval marca el ritmo de las comprobaciones en régimen normal, start_period protege la fase de arranque.

  4. Escribir el `depends_on` con `condition: service_healthy`

    En cada servicio que dependa de la base de datos, sustituya la forma corta de depends_on por la forma larga con condición:

    services:
      app:
        image: monapp:latest
        depends_on:
          db:
            condition: service_healthy
        environment:
          DATABASE_URL: postgresql://app:secret@db:5432/appdb

    Con esta configuración, Docker espera a que el healthcheck del servicio db devuelva healthy antes de arrancar app. Si db pasa a unhealthy tras retries fallos, app no arranca.

  5. Probar con `docker compose up`

    Lance la stack y observe la secuencia:

    docker compose up

    Verá en los logs líneas de este tipo:

    db  | database system is ready to accept connections
    app | Waiting for db to be healthy...
    app | Starting application server

    Para comprobar el estado del healthcheck en cualquier momento:

    docker inspect <nom_du_conteneur_db> | grep -A 5 '"Health"'

    La salida indica Status: healthy, starting o unhealthy, y lista las últimas salidas de la comprobación.

  6. Caso Redis: healthcheck adaptado

    Redis no dispone de redis-isready, pero su equivalente es redis-cli ping:

      redis:
        image: redis:7-alpine
        healthcheck:
          test: ["CMD", "redis-cli", "ping"]
          interval: 5s
          timeout: 3s
          retries: 5
          start_period: 10s

    redis-cli ping devuelve PONG y el código de salida 0 si Redis acepta conexiones. Como Redis arranca más rápido que PostgreSQL, start_period: 10s suele ser suficiente.

  7. Caso MySQL / MariaDB: `mysqladmin ping`

    Para MySQL o MariaDB, use 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: 30s

    Atención: la contraseña se concatena directamente a -p sin espacio (-psecret), es el comportamiento esperado de mysqladmin. Este comando aparece en docker inspect, así que prefiera un secret de Compose o una variable de entorno si la confidencialidad es una restricción.

  8. Resolución de problemas: cuatro errores frecuentes

    pg_isready: command not found — no está usando la imagen oficial postgres (ni una imagen derivada que la incluya). Compruébelo con docker compose exec db which pg_isready.

    Healthcheck en bucle, nunca healthy — el test nunca devuelve 0. Pruébelo manualmente: docker compose exec db pg_isready -U app -d appdb. Si el comando falla, revise las variables de entorno POSTGRES_USER y POSTGRES_DB.

    start_period demasiado corto — en un primer arranque con un volumen vacío, PostgreSQL puede tardar más de 30 segundos. Auméntelo a 60s u observe los logs: database system was shut down at … LOG: database system is ready to accept connections indica el retraso real.

    Contenedor en unhealthy permanente — tras retries fallos, Docker marca el contenedor como unhealthy pero no lo reinicia (eso es tarea de restart). Consulte docker inspect para ver la salida de las últimas comprobaciones e identificar el comando que falla.

Comportamiento por defecto frente a healthcheck

Desplace la tabla

CasoComportamiento por defecto (`service_started`)Con `service_healthy`
Primer arranque, volumen vacíoLa app arranca antes de que la base esté lista → crash loopLa app espera a que PostgreSQL se inicialice y acepte conexiones
Reinicio tras una parada limpiaLa app puede arrancar durante la fase de recovery de PostgreSQLLa app queda en espera hasta el final de la recovery
Base lenta (extensiones, init pesado)Race condition según la velocidad del hostSin race condition: el healthcheck valida el estado real
Dependencias en cascada (app → worker → db)Cada eslabón debe gestionar por su cuenta los reintentos de conexiónLa cadena de condiciones garantiza el orden de arranque
Pruebas de integración en CIResultados intermitentes según la velocidad del runnerResultados deterministas
Redis o MySQL en lugar de PostgreSQLEl mismo problema: `depends_on` por defecto no distingueLa misma solución, con el comando de comprobación adaptado a cada motor

`pg_isready` o `SELECT 1`: ¿cuál elegir?

En la práctica se ven a menudo dos variantes de healthcheck de PostgreSQL:

- ["CMD", "pg_isready", "-U", "postgres"]
- ["CMD-SHELL", "psql -U postgres -c 'SELECT 1'"]"

pg_isready es más fiable por una razón sencilla: solo comprueba la capacidad del servidor para aceptar conexiones TCP, sin abrir una sesión SQL. Devuelve 0 en cuanto el servidor escucha y acepta el saludo de conexión, que es exactamente lo que una aplicación necesita para intentar su propia conexión.

SELECT 1 mediante psql abre una sesión SQL real y ejecuta una consulta. Es una prueba más profunda, pero puede fallar por motivos ajenos a la disponibilidad del servidor (cuota de conexiones alcanzada, pg_hba.conf mal configurado). Para un healthcheck, la prueba mínima y directa es preferible.

Adaptar el healthcheck a Redis y MySQL

Para Redis, sustituya pg_isready por CMD redis-cli PING — el comando devuelve PONG en cuanto el servidor acepta conexiones. Para MySQL o MariaDB, use CMD mysqladmin ping -h localhost -u root --password=$$MYSQL_ROOT_PASSWORD: ajuste start_period: 60s, porque la inicialización de una base MySQL tarda más que la de PostgreSQL. El patrón condition: service_healthy es idéntico sea cual sea el servicio de destino.

Enlaces internos y próximos pasos

El healthcheck service_healthy es uno de los ajustes de robustez que conviene activar en producción. Otros puntos merecen la misma atención antes de un despliegue duradero: la política de reinicio restart: unless-stopped, los límites de recursos deploy.resources.limits y la rotación de logs logging.options. Encontrará la lista de comprobación completa en Docker Compose en producción: 10 puntos que verificar.

Si su stack crece — varios servicios, varios hosts — un reverse proxy como Caddy o Traefik se vuelve imprescindible para gestionar el enrutamiento HTTPS. La guía Caddy, Traefik o Nginx Proxy Manager detalla los criterios de elección según su perfil.

Para automatizar el despliegue de toda la infraestructura (VPS, Docker, configuración) de forma reproducible, Ansible para automatizar sus servidores VPS le guiará paso a paso.

Aloje su stack Docker en un VPS dedicado

Un VPS ServOrbit le da acceso root, una IPv4 dedicada y los recursos necesarios para ejecutar sus stacks Docker Compose en producción. Despliegue en unos minutos.

¿Necesita ayuda?

Consulte nuestro centro de ayuda y nuestra FAQ, o contacte con nuestro equipo: llamada, WhatsApp o correo electrónico. Soporte en francés, inglés y árabe.

Escribir por WhatsAppse abre en una pestaña nueva