Saltar al contenido

Lección 11 de 15 · Lectura y práctica

Health Checks y Reinicio Automático

CONCEPTO CLAVE: Los health checks permiten a Docker monitorear el estado de salud de un container y tomar acciones automáticas cuando algo falla. Combinados con políticas de reinicio, garantizan alta disponibilidad y resiliencia.

Del concepto a la práctica

Taller de esta lección

Un health check debe probar una capacidad útil, mientras que una política de reinicio decide qué hacer después de que el proceso termine. Un contenedor puede estar ejecutándose y seguir atendiendo mal; por eso ambas señales no son equivalentes.

Ejemplo resuelto

dockerfile
FROM nginx:alpine
HEALTHCHECK --interval=10s --timeout=3s --retries=3 CMD wget -q -O - http://localhost/estado.txt || exit 1

La comprobación exige una respuesta HTTP local. Si falla tres veces, el estado cambia a unhealthy; la política de reinicio se define aparte. Docker Engine no reinicia automáticamente por unhealthy: las políticas restart reaccionan a la salida del proceso, no al estado de salud por sí solo.

Pruébalo

  1. Construye la imagen y arráncala.

  2. Espera a que aparezca el estado healthy.

  3. En la imagen local de práctica crea primero estado.txt con ok. Después retira únicamente ese archivo temporal: nginx devolverá 404 y wget fallará, sin detener el proceso principal. Cambiar el contenido a otro texto con HTTP200 no provocaría fallo en este health check.

  4. Observa el estado sin confundirlo con el código de salida del proceso.

Error frecuente: comprobar solo ps o el proceso principal. Corrígelo verificando la ruta o dependencia que realmente necesita el usuario.

Comprueba tu resultado

Registra una transición starting a healthy y, después, una transición a unhealthy provocada localmente. El resultado esperado incluye los códigos de salida de la comprobación y demuestra que un proceso activo no garantiza salud.

Secuencia de código, imagen de contenedor, pruebas, registro y despliegue de instancias idénticas.
CursaloMapa de práctica
Del código al despliegue

El código se empaqueta en una imagen y se prueba antes de publicarlo. Esa misma imagen se guarda en un registro y se usa en el despliegue. Comprobar la salud de las instancias permite detectar un fallo sin reconstruir el artefacto.

Ver la infografía en tamaño completo ↗
CONCEPTO CLAVE: Los health checks permiten a Docker monitorear el estado de salud de un container y tomar acciones automáticas cuando algo falla. Combinados con políticas de reinicio, garantizan alta disponibilidad y resiliencia en entornos de producción.

¿Por qué necesitas Health Checks?

En un entorno de producción, los containers pueden fallar por múltiples razones: errores en la aplicación, agotamiento de memoria, conexiones perdidas a bases de datos, o simplemente procesos zombies. Sin mecanismos de supervisión, un container caído permanece caído hasta que alguien lo descubre manualmente.

Los health checks resuelven este problema permitiendo que Docker interrogue periódicamente al container sobre su estado real de salud. Si el container no responde correctamente, Docker puede reiniciarlo automáticamente según la política configurada.

📌 Nota importante: Un container que está "running" no significa necesariamente que esté funcionando correctamente. Un proceso puede estar activo pero colgado, sin aceptar conexiones, o en un estado de error no detectado.

Configurando Health Checks

Docker proporciona la instrucción HEALTHCHECK en el Dockerfile para definir cómo verificar que un container está saludable. Esta instrucción acepta varios parámetros:

HEALTHCHECK [--interval=30s] [--timeout=10s] [--retries=3] [--start-period=40s] CMD comando

Parámetros del HEALTHCHECK

Parámetro Descripción Valor por defecto
--interval Tiempo entre verificaciones de salud 30 segundos
--timeout Tiempo máximo para considerar la verificación exitosa 30 segundos
--retries Intentos fallidos consecutivos antes de marcar como unhealthy 3
--start-period Tiempo de gracia inicial durante el cual los fallos no cuentan 0 segundos

Ejemplo práctico: API REST con Node.js

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 3000

# Health check: verifica que el endpoint /health responde correctamente
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
  CMD node -e "
    const http = require('http');
    http.get('http://localhost:3000/health', (res) => {
      process.exit(res.statusCode === 200 ? 0 : 1);
    }).on('error', () => process.exit(1));
  "

CMD ["node", "server.js"]

Ejemplo: Base de datos PostgreSQL

FROM postgres:15-alpine

ENV POSTGRES_DB=mydb
ENV POSTGRES_USER=myuser
ENV POSTGRES_PASSWORD=mypassword

# Verificar que PostgreSQL responde a conexiones
HEALTHCHECK --interval=10s --timeout=5s --retries=5 \
  CMD pg_isready -U myuser -d mydb || exit 1

EXPOSE 5432
⚠️ Advertencia: El comando HEALTHCHECK debe terminar con código de salida 0 para indicar éxito y cualquier otro código para indicar fallo. Un health check que nunca termina correctamente hará que el container sea marcado como unhealthy permanentemente.

Políticas de Reinicio Automático

Docker ofrece múltiples políticas de reinicio que determinan qué sucede cuando un container se detiene. Estas políticas se configuran con la bandera --restart al crear el container.

Políticas disponibles

Política Comportamiento Casos de uso
no No reinicia automáticamente (comportamiento por defecto) Containers de desarrollo, tareas únicas
on-failure[:max-retries] Reinicia solo si el container sale con código de error Procesos que pueden fallar recoverablemente
unless-stopped Reinicia siempre excepto si se detuvo manualmente Servicios en producción (recomendado)
always Reinicia siempre, incluso si se detuvo manualmente Servicios críticos, demonios

Ejemplos de uso

# Reiniciar siempre (para demonios y servicios críticos)
docker run -d --name mi-nginx --restart always -p 80:80 nginx

# Reiniciar solo en caso de fallo, máximo 5 intentos
docker run -d --name mi-app --restart on-failure:5 mi-imagen

# Reiniciar siempre excepto si se detuvo manualmente (PRODUCCIÓN)
docker run -d --name mi-api --restart unless-stopped -p 8080:8080 mi-api:latest
💡 Tip profesional: Para servicios en producción, usa unless-stopped en lugar de always. La diferencia es sutil pero importante: si detienes manualmente un container con always, se reiniciará al reiniciar Docker. Con unless-stopped, respetará tu decisión de mantenerlo detenido.

Combinando Health Checks y Reinicio Automático

La verdadera potencia surge cuando combinas ambas características. Cuando un health check falla consecutivamente el número de retries configurado, Docker marca el container como unhealthy. Con la política de reinicio apropiada, Docker puede automáticamente reiniciar el container para intentar recuperarlo.

📌 Flujo de operación: Docker ejecuta el health check → Si falla, incrementa el contador → Si supera el límite de fallos, marca como unhealthy → Si hay política de reinicio, reinicia el container → El ciclo se repite si es necesario.

Ejemplo completo: Aplicación web con base de datos

version: '3.8'

services:
  api:
    build: ./api
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "3000:3000"

  db:
    image: postgres:15-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: secretpassword
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
      interval: 10s
      timeout: 5s
      retries: 5
    
volumes:
  pgdata:

Verificando el estado de salud

# Ver estado de todos los containers incluyendo health
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Health}}"

# Ver detalles del health check de un container específico
docker inspect --format='{{json .State.Health}}' mi-container | jq

# Ver historial de health checks
docker inspect --format='{{range .State.Health.Log}} {{.End}} - Exit: {{.ExitCode}} - Output: {{.Output}} {{end}}' mi-container

Códigos de estado del Health

Un container puede tener uno de estos estados de salud:

Estado Descripción
starting El container está en su período de gracia inicial (start-period)
healthy Todos los health checks han pasado exitosamente
unhealthy El número de fallos consecutivos alcanzó el límite de retries

Casos de uso avanzados

Health check para aplicaciones con autenticación

# Verificar salud sin exponer endpoints de métricas públicos
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:8080/api/health \
    -O /dev/null || exit 1

Health check con verificación de dependencias

Ver más: Verificación de servicios dependientes
#!/bin/sh
set -e

# Verificar que el servicio responde
curl -f http://localhost:8080/health || exit 1

# Verificar conexión a base de datos
nc -z db 5432 || exit 1

echo "All dependencies healthy"
exit 0

Este script verifica tanto la aplicación como sus dependencias externas antes de reportar salud.

Reinicio con límite de intentos

# Detener después de 10 intentos fallidos para evitar loops infinitos
docker run -d \
  --name servicio-critico \
  --restart on-failure:10 \
  --health-cmd="pg_isready -U postgres" \
  --health-interval=30s \
  --health-retries=3 \
  mi-base-datos
⚠️ Importante: Sin límite en on-failure, Docker intentará reiniciar indefinidamente. En casos de bugs críticos que causan crashes constantes, esto puede generar un loop de reinicios que satura los recursos del sistema. Establece un límite máximo de intentos.

Debugging de problemas

  1. Verifica los logs del container: docker logs mi-container para identificar qué está causando los fallos.
  2. Revisa el output del health check: docker inspect --format='{{.State.Health}}' mi-container
  3. Ejecuta el comando manualmente: Entra al container y ejecuta el comando del health check para ver el error directamente.
  4. Verifica recursos: Asegúrate de que el container tiene suficiente CPU y memoria asignados.
  5. Revisa dependencias: Verifica que los servicios externos (bases de datos, APIs) están accesibles.
# Entrar al container y verificar manualmente
docker exec -it mi-container sh

# Dentro del container, ejecutar el health check
curl -f http://localhost:3000/health

# O verificar pg_isready si es PostgreSQL
pg_isready -U postgres

Integración con orquestación

En entornos con Docker Swarm o Kubernetes, los health checks se integran con los mecanismos de orquestación:

📌 Docker Swarm: Los containers marcados como unhealthy no reciben nuevo tráfico en servicios con modo replicated. Swarm puede automáticamente reiniciar containers en nodos que reportan failure.
# En Docker Swarm, crear servicio con health check
docker service create \
  --name mi-servicio \
  --health-cmd="curl -f http://localhost:3000/health || exit 1" \
  --health-interval=30s \
  --health-retries=3 \
  --health-timeout=10s \
  --replicas=3 \
  -p 3000:3000 \
  mi-imagen:latest
"Los sistemas distribuidos tolerantes a fallos asumen que los componentes fallarán eventualmente. Diseña tus servicios asumiendo que necesitarán reiniciarse y asegura que el reinicio los lleve a un estado consistente."
💡 Tip final: Nunca uses exit 0 hardcodeado en tus health checks. Asegúrate de que el comando realmente verifique la funcionalidad de tu aplicación. Un health check que siempre pasa pero no verifica nada es peor que no tener ninguno, porque crea una falsa sensación de seguridad.

Resumen de mejores prácticas

Área Recomendación
Intervalo No más de 30-60 segundos para servicios críticos. Verificaciones muy frecuentes consumen recursos.
Timeout Debe ser menor que el intervalo para permitir reintentos.
Retries 3-5 intentos son suficientes. Muy pocos pueden causar falsos positivos.
Start period Configúralo para aplicaciones que tardan en inicializar.
Reinicio Usa unless-stopped para producción, on-failure:N con límite para evitar loops.
🧠 Quiz: Health Checks y Reinicio Automático

¿Cuál es la diferencia principal entre las políticas de reinicio always y unless-stopped?

  • A) No hay diferencia, ambas reinician siempre
  • B) always reinicia incluso después de un stop manual, mientras unless-stopped respeta los stops manuales hasta el próximo reinicio de Docker
  • C) unless-stopped solo reinicia una vez
  • D) always es para desarrollo y unless-stopped para producción
✅ Respuesta correcta: B) La diferencia clave es que always ignora los stops manuales y reinicia el container al reiniciar el daemon de Docker, mientras unless-stopped respeta la decisión de detenerlo manualmente, manteniendo el container detenido incluso después de reiniciar Docker.
🧠 Quiz: Configuración de Health Checks

¿Qué sucede cuando un health check alcanza el número máximo de fallos configurado en --retries?

  • A) El container se elimina automáticamente
  • B) El container se marca como unhealthy y puede ser reiniciado según la política de reinicio configurada
  • C) Docker envía un email al administrador
  • D) Nada, los retries solo afectan los logs
✅ Respuesta correcta: B) Cuando se alcanzan los fallos consecutivos máximo, Docker marca el container como unhealthy. Lo que sucede después depende de la política de reinicio: si es always o unless-stopped, Docker reiniciará el container. Si es on-failure, también reiniciará porque el exit code será diferente de 0.
# Comando rápido para verificar todos los health checks de tus containers
docker ps --filter "health=unhealthy" --format "table {{.Names}}\t{{.Status}}"

Sigue con Docker para Desarrolladores

Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.

Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.

01 / ELEGIR UNA RUTA

El próximo paso

Aprende IA mientras construyes algo que sí necesitas.

Empieza por el curso inicial o explora el catálogo para encontrar una práctica alineada con tu trabajo.