--- title: "Cómo configurar healthchecks en Docker y Docker Compose" description: "Añade healthchecks con HEALTHCHECK en un Dockerfile y en Docker Compose, consulta el estado de cada contenedor y arranca servicios solo cuando estén sanos." category: "Docker Avanzado" language: es source: https://cubepath.com/es/docs/docker-avanzado/configuracion-de-verificaciones-de-salud-de-docker updated: 2026-09-25 --- # Cómo configurar healthchecks en Docker y Docker Compose Un contenedor puede estar en ejecución y aun así no funcionar: el proceso sigue vivo, pero la aplicación no responde o ha perdido la conexión con la base de datos. Los healthchecks de Docker ejecutan periódicamente un comando dentro del contenedor y marcan su estado como `healthy` o `unhealthy`. En este tutorial añadirás un healthcheck a una aplicación propia, provocarás un fallo para ver cómo cambia el estado y usarás Docker Compose para que un servicio no arranque hasta que su base de datos esté lista, todo en Ubuntu 24.04. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con un usuario no root con privilegios `sudo`. - Docker Engine 25 o posterior y el plugin de Docker Compose instalados desde el repositorio oficial de Docker. - Tu usuario en el grupo `docker`, o anteponer `sudo` a los comandos `docker`. Comprueba las versiones antes de empezar: ```bash docker version --format '{{.Server.Version}}' docker compose version ``` ## Cómo funciona un healthcheck Docker ejecuta el comando de comprobación dentro del contenedor cada cierto intervalo. Si el comando sale con código `0`, la comprobación es correcta; si sale con `1`, es un fallo. Tras varios fallos seguidos el contenedor pasa a `unhealthy`. Un contenedor con healthcheck tiene uno de estos tres estados: | Estado | Significado | |---|---| | `starting` | El contenedor acaba de arrancar y aún no ha superado ninguna comprobación. | | `healthy` | La última comprobación ha sido correcta. | | `unhealthy` | Han fallado `retries` comprobaciones seguidas. | Estas son las opciones disponibles y sus valores por defecto: | Opción | Por defecto | Uso | |---|---|---| | `interval` | 30s | Tiempo entre comprobaciones. | | `timeout` | 30s | Tiempo máximo que puede tardar el comando; si se supera, cuenta como fallo. | | `retries` | 3 | Fallos seguidos necesarios para marcar el contenedor como `unhealthy`. | | `start_period` | 0s | Margen de arranque: los fallos durante este periodo no cuentan. | | `start_interval` | 5s | Intervalo entre comprobaciones durante el `start_period` (Docker 25 o posterior). | > Importante: Docker Engine por sí solo solo informa del estado. Un contenedor `unhealthy` no se reinicia automáticamente, ni siquiera con `--restart always`. Quien actúa sobre ese estado es Docker Swarm (que sustituye la tarea), Docker Compose con `depends_on` o tu sistema de monitorización. ## Paso 1: Probar un healthcheck desde la línea de comandos Antes de tocar ningún Dockerfile, puedes definir un healthcheck al lanzar un contenedor con las opciones `--health-*` de `docker run`. Este ejemplo arranca Redis y comprueba que responde a `PING`: ```bash docker run -d --name redis-test \ --health-cmd "redis-cli ping || exit 1" \ --health-interval 10s \ --health-timeout 3s \ --health-retries 3 \ --health-start-period 5s \ redis:7 ``` Consulta el estado con `docker ps`. En los primeros segundos verás `health: starting` y después `healthy`: ```bash docker ps --filter name=redis-test ``` ```output CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3f2a1c9d8e7b redis:7 "docker-entrypoint.s…" 15 seconds ago Up 14 seconds (healthy) 6379/tcp redis-test ``` El `|| exit 1` convierte cualquier código de error en `1`, que es el único valor de fallo que Docker espera. Elimina el contenedor de prueba: ```bash docker rm -f redis-test ``` ## Paso 2: Añadir HEALTHCHECK a un Dockerfile Lo habitual es declarar el healthcheck en la propia imagen, para que cualquiera que la ejecute lo herede. Vas a crear una pequeña aplicación HTTP en Python con un endpoint `/health` que devuelve `200` mientras no exista el archivo `/tmp/unhealthy`, lo que te permitirá simular un fallo en el paso 4. Crea un directorio para el proyecto: ```bash mkdir -p ~/healthcheck-demo && cd ~/healthcheck-demo ``` Crea el archivo de la aplicación: ```bash nano app.py ``` ```python import os from http.server import BaseHTTPRequestHandler, HTTPServer class Handler(BaseHTTPRequestHandler): def do_GET(self): if self.path == "/health": if os.path.exists("/tmp/unhealthy"): self.send_response(503) self.end_headers() self.wfile.write(b"unhealthy\n") return self.send_response(200) self.end_headers() self.wfile.write(b"ok\n") return self.send_response(200) self.end_headers() self.wfile.write(b"Hola desde el contenedor\n") HTTPServer(("0.0.0.0", 8000), Handler).serve_forever() ``` Ahora crea el Dockerfile: ```bash nano Dockerfile ``` ```dockerfile FROM python:3.12-slim WORKDIR /app COPY app.py . USER nobody EXPOSE 8000 HEALTHCHECK --interval=10s --timeout=3s --start-period=10s --retries=3 \ CMD python -c "import urllib.request, sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=2).status == 200 else 1)" || exit 1 CMD ["python", "app.py"] ``` La imagen `python:3.12-slim` no incluye `curl` ni `wget`, así que el healthcheck usa el propio intérprete de Python. Es una buena práctica general: usa una herramienta que ya exista en la imagen en lugar de instalar `curl` solo para esto. Si la petición devuelve un `503`, `urlopen` lanza una excepción, Python sale con un código distinto de cero y `|| exit 1` lo normaliza a `1`. Construye la imagen: ```bash docker build -t healthcheck-demo . ``` Verifica que el healthcheck ha quedado registrado en la imagen: ```bash docker image inspect healthcheck-demo --format '{{json .Config.Healthcheck}}' ``` ```output {"Test":["CMD-SHELL","python -c \"import urllib.request, sys; ...\" || exit 1"],"Interval":10000000000,"Timeout":3000000000,"StartPeriod":10000000000,"Retries":3} ``` Los tiempos aparecen en nanosegundos. ## Paso 3: Ejecutar el contenedor y consultar su estado Arranca un contenedor a partir de la imagen: ```bash docker run -d --name web -p 8000:8000 healthcheck-demo ``` Espera unos segundos y comprueba el estado: ```bash docker inspect web --format '{{.State.Health.Status}}' ``` ```output healthy ``` Docker guarda el resultado de las últimas cinco comprobaciones, con su código de salida y la salida del comando. Es lo primero que debes mirar cuando un contenedor aparece como `unhealthy`: ```bash docker inspect web --format '{{json .State.Health}}' | python3 -m json.tool ``` ```output { "Status": "healthy", "FailingStreak": 0, "Log": [ { "Start": "2026-09-25T10:12:31.402118Z", "End": "2026-09-25T10:12:31.498233Z", "ExitCode": 0, "Output": "" } ] } ``` ## Paso 4: Simular un fallo Crea el archivo que hace que `/health` devuelva `503`: ```bash docker exec web touch /tmp/unhealthy ``` Con `interval=10s` y `retries=3`, el contenedor pasará a `unhealthy` en unos 30 segundos. Puedes seguir el cambio en tiempo real con los eventos de Docker (pulsa `Ctrl+C` para salir): ```bash docker events --filter container=web --filter event=health_status ``` ```output 2026-09-25T10:14:02.118Z container health_status: unhealthy 8c1d... (image=healthcheck-demo, name=web) ``` Confirma el estado y el contador de fallos: ```bash docker inspect web --format '{{.State.Health.Status}} {{.State.Health.FailingStreak}}' ``` ```output unhealthy 3 ``` Observa que el contenedor sigue en ejecución (`docker ps` muestra `Up ... (unhealthy)`). Docker no lo reinicia. Borra el archivo y en la siguiente comprobación volverá a `healthy`: ```bash docker exec web rm /tmp/unhealthy ``` Cuando termines, elimina el contenedor: ```bash docker rm -f web ``` ## Paso 5: Healthchecks en Docker Compose En Compose el healthcheck se define por servicio con la clave `healthcheck`, y otros servicios pueden esperar a que esté sano con `depends_on` y `condition: service_healthy`. Así evitas el clásico error de una aplicación que arranca antes de que PostgreSQL acepte conexiones. Crea el archivo `compose.yaml` en el mismo directorio: ```bash nano compose.yaml ``` ```yaml services: db: image: postgres:16 environment: POSTGRES_USER: app POSTGRES_PASSWORD: your_strong_password POSTGRES_DB: app volumes: - db_data:/var/lib/postgresql/data healthcheck: test: ["CMD", "pg_isready", "-U", "app", "-d", "app"] interval: 10s timeout: 5s retries: 5 start_period: 30s start_interval: 2s web: build: . ports: - "8000:8000" depends_on: db: condition: service_healthy volumes: db_data: ``` Sustituye `your_strong_password` por una contraseña robusta. Algunos detalles de este archivo: - `pg_isready` viene incluido en la imagen oficial de PostgreSQL y sale con `0` solo cuando el servidor acepta conexiones. - La forma `["CMD", ...]` ejecuta el comando directamente, sin shell. Usa `["CMD-SHELL", "..."]` cuando necesites operadores como `||` o variables de entorno. - El servicio `web` no define `healthcheck`, porque hereda el `HEALTHCHECK` de su Dockerfile. Si quisieras desactivarlo en Compose usarías `healthcheck: { disable: true }`. Levanta la pila con `--wait`, que hace que Compose no devuelva el control hasta que todos los servicios estén en ejecución y sanos: ```bash docker compose up -d --build --wait ``` ```output ✔ Container healthcheck-demo-db-1 Healthy ✔ Container healthcheck-demo-web-1 Healthy ``` Comprueba el estado de ambos servicios: ```bash docker compose ps ``` ```output NAME IMAGE SERVICE STATUS PORTS healthcheck-demo-db-1 postgres:16 db Up 40 seconds (healthy) 5432/tcp healthcheck-demo-web-1 healthcheck-demo-web web Up 28 seconds (healthy) 0.0.0.0:8000->8000/tcp ``` El servicio `web` arrancó solo cuando `db` pasó a `healthy`. `--wait` es también útil en scripts de despliegue: si algún servicio acaba en `unhealthy`, el comando devuelve un código de error. ## Paso 6: Buenas prácticas para el comando de comprobación Un healthcheck mal diseñado genera falsos positivos o carga innecesaria. Ten en cuenta lo siguiente: - **Comprueba la aplicación, no solo el proceso.** Un endpoint `/health` que valide lo imprescindible (por ejemplo, que la aplicación puede abrir una conexión a su base de datos) es mejor que comprobar que un puerto está abierto. - **Mantén el comando ligero.** Se ejecuta cada `interval` en cada réplica. Evita consultas pesadas o llamadas a servicios externos. - **Ajusta `start_period` al arranque real.** Aplicaciones Java o con migraciones pueden tardar un minuto en estar listas; sin margen, se marcarán como `unhealthy` antes de terminar de arrancar. - **Usa `timeout` menor que `interval`.** Si no, las comprobaciones se solapan con la siguiente. - **No dependas de herramientas ausentes.** Si la imagen es mínima (distroless, `scratch`), incluye un pequeño binario de comprobación en la propia aplicación, por ejemplo un subcomando `app healthcheck`. ## Solución de problemas **El contenedor está siempre en `starting`.** El comando nunca ha tenido éxito y el `start_period` todavía no ha terminado. Revisa `.State.Health.Log` con `docker inspect`: la salida suele indicar el problema. **`exec: "curl": executable file not found`.** La imagen no incluye `curl`. Usa una herramienta que exista en la imagen (`wget` en imágenes Alpine, `python` en imágenes de Python, `pg_isready` en PostgreSQL) o instálala en el Dockerfile. **El healthcheck funciona a mano pero falla en Docker.** El comando se ejecuta con el usuario definido en `USER` y sin tu entorno de shell. Pruébalo exactamente igual con `docker exec nombre_contenedor sh -c 'comando'`. **Compose arranca el servicio aunque la base de datos no esté lista.** Revisa que `depends_on` use la forma larga con `condition: service_healthy`; la lista simple (`depends_on: [db]`) solo espera a que el contenedor arranque. ## Conclusión Has añadido un healthcheck a una imagen propia, has visto cómo pasa de `healthy` a `unhealthy` y cómo diagnosticarlo con `docker inspect`, y has usado `depends_on` con `service_healthy` y `docker compose up --wait` para ordenar el arranque de una pila. Como siguientes pasos, puedes desplegar la pila en Docker Swarm para que sustituya automáticamente las tareas que fallen, enviar los eventos `health_status` a tu sistema de alertas o combinar los healthchecks con un proxy inverso que solo envíe tráfico a contenedores sanos.