Problema

En entornos donde Docker es el motor de ejecución principal, mantener los contenedores al día es una tarea constante. Cada nueva etiqueta de imagen puede introducir vulnerabilidades, cambios incompatibles o simplemente mejoras de rendimiento. Sin embargo, la mayoría de los equipos se encuentran con dos limitaciones habituales:

  1. Actualizaciones inesperadas que rompen servicios porque no se validó la compatibilidad antes de aplicar la nueva imagen.
  2. Falta de visibilidad sobre qué contenedores tienen versiones desactualizadas y qué tan críticas son esas versiones.

El resultado típico es una mezcla de parches manuales, rollback de emergencia y, en el peor de los casos, exposición prolongada a vulnerabilidades conocidas. La necesidad de un proceso que detecte, evalúe y aplique actualizaciones de forma controlada es común en cualquier infraestructura basada en Docker, ya sea un homelab, un clúster de producción o un servidor de pruebas.

Causa

Los fallos en la gestión de actualizaciones de contenedores suelen rastrearse a tres causas principales:

  • Política de actualización inexistente o demasiado laxa. Cuando se permite que cualquier nueva etiqueta sea descargada y ejecutada sin filtro, se corre el riesgo de introducir cambios de versión mayor o imágenes comprometidas.
  • Ausencia de pruebas de integridad. Sin escaneo de vulnerabilidades (por ejemplo, Trivy o Grype) o verificación de firmas (Cosign), la cadena de suministro de la imagen puede estar contaminada.
  • Rollback manual o inexistente. Si la actualización falla y no se dispone de una instantánea del estado previo, el tiempo de inactividad se prolonga mientras se diagnostica y se restaura manualmente.

En entornos con Docker‑Compose, el problema se agrava porque varios servicios dependen entre sí y una actualización parcial puede dejar al stack en un estado inconsistente. Además, cuando los hosts están distribuidos, la falta de un agente remoto obliga a abrir puertos innecesarios o a ejecutar scripts ad‑hoc en cada nodo.

Solución

Una solución reutilizable se basa en tres pilares: detección, validación y aplicación controlada. Drydock implementa exactamente ese flujo, pero el enfoque puede reproducirse con otras herramientas o scripts personalizados siguiendo los mismos principios.

1. Detección automática de versiones

Utilice la API del registro (Docker Hub, GHCR, ECR, etc.) para comparar el digest de la imagen en ejecución con el digest más reciente disponible. Un script sencillo en Bash o Python puede listar los contenedores, extraer sus etiquetas y consultar el registro:

#!/usr/bin/env bash
for cid in $(docker ps -q); do
  img=$(docker inspect --format '{{.Config.Image}}' "$cid")
  repo=$(echo "$img" | cut -d':' -f1)
  tag=$(echo "$img" | cut -d':' -f2)
  latest=$(skopeo inspect docker://$repo:latest | jq -r .Digest)
  current=$(docker inspect --format '{{.Image}}' "$cid")
  if [ "$latest" != "$current" ]; then
    echo "Container $cid ($img) has newer digest $latest"
  fi
done

Este fragmento identifica rápidamente los contenedores que están desactualizados sin necesidad de herramientas externas.

2. Validación de seguridad antes de actualizar

Integre un escáner de vulnerabilidades (Trivy o Grype) en el pipeline. La idea es descargar la imagen candidata en modo offline y ejecutar el escáner con una política de severidad mínima. Si la imagen supera el umbral, el proceso se detiene.

candidate="myrepo/app:1.4.2"
docker pull "$candidate"
trivy image --severity HIGH,CRITICAL "$candidate" > /tmp/trivy_report.txt
if grep -q "CRITICAL" /tmp/trivy_report.txt; then
  echo "Imagen $candidate rechazada por vulnerabilidades críticas"
  exit 1
fi

Para firmas, Cosign verifica la integridad:

cosign verify "$candidate"

3. Backup y rollback automático

Antes de reemplazar una imagen, cree una etiqueta de respaldo con el digest actual. Docker permite crear una nueva etiqueta apuntando al mismo digest:

current_digest=$(docker inspect --format '{{.Image}}' "$cid")
docker tag "$current_digest" "$repo:backup-$(date +%Y%m%d%H%M)"

Después de iniciar el contenedor con la nueva imagen, monitorice la salud mediante docker healthcheck. Si falla, vuelva a la etiqueta de respaldo:

if ! docker inspect --format '{{.State.Health.Status}}' "$cid" | grep -q healthy; then
  echo "Healthcheck falló, restaurando backup"
  docker service update --image "$repo:backup-$(date +%Y%m%d%H%M)" "$service_name"
fi

4. Soporte para Docker‑Compose

Cuando el stack está definido en docker-compose.yml, la actualización se realiza mediante docker compose pull seguido de docker compose up -d. Para preservar la estructura del YAML y solo cambiar la imagen, use la opción --profile o edite dinámicamente la sección image con yq antes de ejecutar el despliegue.

yq eval '.services.app.image = "myrepo/app:1.4.2"' -i docker-compose.yml
docker compose pull app && docker compose up -d app

5. Agentes remotos ligeros

En entornos multi‑host, despliegue un agente Docker (por ejemplo, un contenedor con acceso limitado al socket) que exponga una API REST o WebSocket. El nodo central consulta los agentes, recoge el estado y dispara actualizaciones sin abrir puertos de Docker directamente. La comunicación puede asegurarse con OIDC o tokens estáticos, y los agentes pueden ejecutarse bajo un usuario no privilegiado.

Cuándo aplicar esta solución

Esta arquitectura es adecuada cuando:

  • Se manejan varios contenedores críticos y la exposición a vulnerabilidades debe minimizarse.
  • Los despliegues se hacen con Docker‑Compose o stacks simples, sin orquestadores complejos como Kubernetes.
  • Se necesita rollback rápido porque el tiempo de inactividad impacta a usuarios finales o a procesos de negocio.
  • Los hosts están distribuidos y no es viable abrir puertos de Docker en cada nodo.

No es la mejor opción si ya se usa un orquestador con políticas de actualización nativas (por ejemplo, Argo Rollouts en Kubernetes) o si la infraestructura está totalmente gestionada por un proveedor que ya implementa escaneo de imágenes y rollback.

Código

# 1. Detectar contenedores desactualizados
outdated=$(bash detect_outdated.sh)

# 2. Para cada contenedor, validar seguridad y firmar
for img in $outdated; do
  docker pull "$img"
  trivy image --severity HIGH,CRITICAL "$img" || continue
  cosign verify "$img" || continue

  # 3. Crear backup
  cid=$(docker ps -q --filter "ancestor=$img")
  backup_tag="${img%:*}:backup-$(date +%Y%m%d%H%M)"
  docker tag "$img" "$backup_tag"

  # 4. Actualizar con Compose (asumiendo service llamado app)
  yq eval ".services.app.image = \"$img\"" -i docker-compose.yml
  docker compose pull app && docker compose up -d app

  # 5. Verificar healthcheck y rollback si necesario
  if ! docker inspect --format '{{.State.Health.Status}}' "$cid" | grep -q healthy; then
    docker compose up -d --no-deps --force-recreate app
    docker compose up -d app
  fi
done

Verificación

  1. Listado de versiones – Ejecute detect_outdated.sh y confirme que la salida sea vacía después de una actualización exitosa.
  2. Escaneo de vulnerabilidades – Revise los archivos de reporte (/tmp/trivy_report.txt) y asegúrese de que no haya hallazgos críticos.
  3. Estado de saluddocker inspect --format '{{.State.Health.Status}}' <container> debe devolver healthy para todos los contenedores.
  4. Rollback – Simule una falla modificando temporalmente el healthcheck y verifique que el contenedor vuelva a la etiqueta backup-….

Notas adicionales

  • Mantenimiento de ventanas – Defina horarios de actualización en cron (por ejemplo, 0 3 * * 0) para evitar interrupciones en horarios pico.
  • Política de madurez – Ignorar versiones que tengan menos de N días de vida reduce el ruido de actualizaciones tempranas.
  • Auditoría – Registre cada acción en un archivo de log estructurado (JSON) para cumplir con requisitos de compliance.
  • Escalabilidad – Si el número de hosts supera decenas, considere agrupar agentes bajo un reverse proxy con autenticación mutua para simplificar la gestión de certificados.