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:
- Actualizaciones inesperadas que rompen servicios porque no se validó la compatibilidad antes de aplicar la nueva imagen.
- 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
- Listado de versiones – Ejecute
detect_outdated.shy confirme que la salida sea vacía después de una actualización exitosa. - Escaneo de vulnerabilidades – Revise los archivos de reporte (
/tmp/trivy_report.txt) y asegúrese de que no haya hallazgos críticos. - Estado de salud –
docker inspect --format '{{.State.Health.Status}}' <container>debe devolverhealthypara todos los contenedores. - 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.