|

Docker hardened en Java: 75,5 % menos paquetes

En pocas palabras: Para asegurar una imagen Docker de Java y reducir CVE, cambiá la base eclipse-temurin:21-jre por una Docker Hardened Image: en la corrida del 13 de octubre de 2026 el inventario de runtime bajó 75,5 %, sin tocar dependencias. Las CVE de tu aplicación no desaparecen.

Cambiar la imagen base de eclipse-temurin:21-jre a la Docker Hardened Image equivalente bajó el inventario de runtime de una app Spring Boot de 192 a 47 paquetes (75,5 % menos) y dejó en cero los hallazgos del sistema operativo, según una corrida del 13/10/2026 sobre seguridad en imágenes Docker.

Docker Hardened Images (DHI) es un catálogo de más de 1.000 imágenes de contenedor mínimas, basadas en Debian y Alpine, con licencia Apache 2.0, que Docker liberó sin costo. Cada imagen trae SBOM, provenance SLSA Build Level 3 y firma criptográfica. Sirven de base para tus Dockerfile, incluidos los de Java. Reemplazan imágenes como eclipse-temurin, pero no corrigen las dependencias de tu propia aplicación.

En 30 segundos

  • El inventario de runtime bajó de 192 a 47 paquetes (75,5 % menos) al cambiar solo la imagen base.
  • Después del cambio quedan 3 hallazgos críticos, 5 altos y 2 medios en dependencias Java (Tomcat y Jackson, según la fuente).
  • El SBOM de un multi-stage solo cubre la etapa final, salvo que agregues BUILDKIT_SBOM_SCAN_STAGE=true.
  • Cosign prueba autenticidad del digest, no ausencia de CVE. Para eso va un gate de Trivy.

¿Cuántos paquetes y CVE elimina cambiar la imagen base por una Docker Hardened Image?

La corrida (Spring Boot 4.0.8, Java 21, Trivy 0.75.0, resultados idénticos en linux/amd64 y linux/arm64) comparó eclipse-temurin:21-jre contra dhi.io/eclipse-temurin:21-alpine3.23. El inventario de runtime pasó de 192 a 47 paquetes sin tocar las dependencias de la aplicación. En paquetes del sistema operativo, la base original tenía 40 hallazgos medios y 4 bajos. La hardened, ninguno.

Métricaeclipse-temurin:21-jredhi.io/eclipse-temurin:21-alpine3.23
Paquetes en el inventario de runtime19247
Hallazgos de paquetes del SO0 críticos, 0 altos, 40 medios, 4 bajosNinguno
Hallazgos en dependencias Java3 críticos, 5 altos, 2 medios3 críticos, 5 altos, 2 medios
seguridad en imágenes Docker diagrama explicativo

Dos aclaraciones antes de entusiasmarte. La nota la escribe alguien del ecosistema Docker sobre un producto de Docker, y los números son de su corrida: las bases y las bases de datos de vulnerabilidades cambian, así que los tuyos pueden diferir. Y la base original no tenía críticos ni altos en el sistema operativo, así que lo que se limpió fueron 44 hallazgos medios y bajos.

Según el autor, las DHI vienen con casi cero CVE, usuario no root y sin shell ni gestor de paquetes en las imágenes de producción. Eso le deja poco material a un atacante que logre entrar al contenedor. El Dockerfile del ejemplo compila con la variante dhi.io/maven:3-jdk21-dev y ejecuta con la imagen de runtime. Una inferencia mía, no de la fuente: sin shell, depurar con un exec interactivo deja de ser una opción y tenés que planificar otra forma.

¿Por qué la seguridad en imágenes Docker no termina al cambiar la base?

Porque las dependencias de tu aplicación viajan igual. En las dos imágenes de la corrida quedaron 3 críticos, 5 altos y 2 medios en librerías Java (Tomcat y Jackson, según la fuente). Cambiar la base no los toca, y por eso el gate de Trivy sigue devolviendo exit code 1 incluso con la imagen hardened.

Para leer esos números con cuidado, la propia nota advierte tres cosas:

  • Severidad no es explotabilidad. Un crítico en una librería que tu código nunca invoca pesa distinto que uno en el camino de una request.
  • Los totales no son CVE únicos. Son registros de Trivy agrupados por target, así que un mismo CVE puede contar varias veces.
  • Cada escáner cuenta a su manera. Scout y Trivy dieron totales distintos sobre la misma baseline. Comparás imágenes dentro del mismo escáner o no comparás.

Qué hacer: subir la versión de Spring Boot o fijar la de la dependencia afectada en el pom.xml, y recién después volver a escanear. Es mi recomendación, no un paso de la fuente. Para más detalles técnicos, mirá imágenes Docker endurecidas y su seguridad probada.

¿Por qué el SBOM de la imagen final no muestra todo lo que se usó en el build?

Con --sbom=true en un build multi-stage, BuildKit inventaría solo la etapa final. Los paquetes que existieron únicamente al compilar desaparecen del inventario cuando se descarta esa etapa. Agregando ARG BUILDKIT_SBOM_SCAN_STAGE=true en la etapa de build, el SBOM suma un documento sbom-build con esos paquetes.

FROM maven:3.9-eclipse-temurin-21 AS build
ARG BUILDKIT_SBOM_SCAN_STAGE=true

Ojo: el argumento agrega metadata, no significa que esos paquetes terminen en la imagen final. Pensalo así: compilás con Maven, bajás cien dependencias, el multi-stage tira esa etapa a la basura, el SBOM final sale prolijo, el auditor lo revisa y nadie sabe qué toolchain tocó el jar (ejemplo hipotético, pero es el hueco que la nota describe).

Conviene distinguir dos documentos. El SBOM responde qué software hay en la imagen. El provenance responde cómo y con qué materiales se construyó.

Hay una advertencia de seguridad: --provenance=mode=max puede exponer build args, así que las credenciales van en secret mounts de BuildKit, nunca en argumentos. Log4Shell (CVE-2021-44228) es el recordatorio de por qué importa saber qué dependencias enviás. Y un SBOM, por sí solo, no vuelve segura a tu aplicación.

¿Sirve firmar una imagen con Cosign si todavía tiene vulnerabilidades?

Sirve para otra cosa. Una firma de Cosign sobre el digest confirma quién publicó la imagen y que no cambió desde entonces; no dice nada sobre CVE. Según la fuente, firmar y endurecer son controles necesarios pero no suficientes, y una attestation adjunta no es automáticamente una firma criptográfica de confianza. Relacionado: proteger tus contenedores con un WAF.

El digest importa porque un tag como :hardened es mutable. Un esquema genérico (no es el flujo del repo, revisá los flags de tu versión de Cosign, la nota usó la 3.1.3):

cosign generate-key-pair
cosign sign --key cosign.key --allow-insecure-registry "$IMAGE_REPO@$DIGEST"
cosign verify --key cosign.pub --allow-insecure-registry "$IMAGE_REPO@$DIGEST"
trivy image --scanners vuln --severity CRITICAL,HIGH --exit-code 1 "$IMAGE_REPO@$DIGEST"

El digest sale del archivo de metadata que genera --metadata-file en el build. El --allow-insecure-registry es solo por el registry local de la demo. La última línea es el gate: aunque la firma verifique, la imagen no se promueve si Trivy encuentra críticos o altos.

¿Hay datos para comparar con distroless o Chainguard?

No en estas fuentes. La nota compara una sola pareja (Temurin contra su equivalente DHI) y Docker, en su página de producto, compara con “otros” proveedores sin números verificables. Si evaluás distroless u otra alternativa, medí con el mismo escáner y los mismos criterios que abajo.

¿Cómo reproducir la prueba en tu máquina o en CI?

Clonás la rama start del repo de ejemplo y seguís los pasos. Docker compila y testea la app, así que no necesitás Java ni Maven local. Requisitos que lista la nota:

  • Docker con Buildx y, si no usás Docker Desktop, el CLI de Scout.
  • Trivy 0.75.0 y Cosign 3.1.3, las versiones de la corrida.
  • Dos logins: docker login para Scout y docker login dhi.io para las imágenes hardened.
  • Apple Silicon: cambiá PLATFORM a linux/arm64.
docker run -d --name dockerize-java-demo-registry -p 127.0.0.1:15000:5000 registry:3
export PLATFORM=linux/amd64
export IMAGE_REPO=127.0.0.1:15000/dockerize-java-securely
docker buildx build --platform "$PLATFORM" --sbom=true --provenance=mode=max --push -t "$IMAGE_REPO:hardened" .

El smoke test del repo corre la imagen con --read-only, --tmpfs /tmp, --cap-drop ALL y no-new-privileges:true, y espera “Hello, Docker Security!” como usuario 10001:10001. El .dockerignore es una allowlist (ignora todo y habilita pom.xml, src/ y los Dockerfile), y el POM fija finalName en app para evitar un wildcard ambiguo del JAR. Ese registry HTTP sin autenticación es solo para demos locales.

Un criterio de verificación, propuesta editorial que no viene de la fuente ni probamos nosotros:

  • Mismo escáner y misma versión para ambas imágenes, el mismo día.
  • Reportes en JSON agrupados por target, no un total suelto.
  • Hallazgos Java idénticos entre baseline y hardened. Si difieren, cambiaste algo más que la base.
  • Repetir a la semana, porque las bases de datos de vulnerabilidades se mueven.

¿Qué está confirmado y qué no?

Confirmado por las fuentes:

  • La corrida del 13/10/2026 midió 192 contra 47 paquetes y los hallazgos de la tabla, con resultados idénticos en amd64 y arm64.
  • El catálogo DHI es gratuito, con licencia Apache 2.0 y más de 1.000 imágenes (reporte de Help Net Security del 22/12/2025).
  • La versión paga Select arranca en USD 5.000 por repositorio y promete fixes críticos en menos de 7 días, según Docker.
  • “La seguridad tiene que empezar en el punto más temprano del desarrollo”, dijo Mark Cavage, presidente y COO de Docker (traducción de la cita original).

No confirmado:

  • Que otro equipo obtenga los mismos números con su propia app (¿alguien lo replicó de forma independiente? En las fuentes que revisé, no hay réplica).
  • Cuántos de los 10 hallazgos Java son explotables en tu código.
  • Cómo se comparan las DHI con distroless u otras imágenes mínimas.

Errores comunes

  • Creer que la imagen “hardened” cierra el tema. Quedaron 10 hallazgos Java. Corrección: gate de Trivy más actualización de dependencias.
  • Comparar totales de Scout contra Trivy. Dieron distinto sobre la misma imagen. Corrección: un escáner, dos imágenes.
  • Confiar en el SBOM de la etapa final. Se pierde lo que se usó al compilar. Corrección: BUILDKIT_SBOM_SCAN_STAGE=true.
  • Poner secretos en build args con provenance máximo. Pueden quedar expuestos. Corrección: secret mounts.
  • Firmar el tag en vez del digest. El tag se puede mover. Corrección: firmá y verificá por digest.

Preguntas Frecuentes

¿Cómo reducir los CVE de una imagen Docker de Java?

Combiná una base mínima, dependencias actualizadas y un gate de Trivy en CI. En la corrida citada, solo la base bajó el inventario de runtime 75,5 %, pero las dependencias Java siguieron con 3 críticos, 5 altos y 2 medios. Ya lo cubrimos antes en configurar Docker Desktop en Windows.

¿Qué son las Docker Hardened Images y cuánto reducen la superficie de ataque?

Son imágenes mínimas y gratuitas (Apache 2.0) con casi cero CVE, SBOM y provenance SLSA Build Level 3. En la prueba pasaron de 192 a 47 paquetes de runtime, un 75,5 % menos, aunque es una sola corrida.

¿Qué es un SBOM y cómo se genera con docker buildx?

Un SBOM es la lista de componentes de software de una imagen. Con docker buildx build --sbom=true se adjunta como attestation, y con BUILDKIT_SBOM_SCAN_STAGE=true en la etapa de build se suma el documento sbom-build.

¿Cambiar la imagen base elimina todas las vulnerabilidades de mi aplicación Spring Boot?

No. La base solo cubre los paquetes del sistema y del runtime. Las vulnerabilidades en librerías como Tomcat o Jackson se corrigen actualizando versiones en el pom.xml.

¿Para qué sirve firmar una imagen con Cosign?

Sirve para verificar que el digest lo publicó quien dice y que no se alteró. No certifica que la imagen esté libre de CVE, por eso se combina con el escaneo.

Conclusión

Cambiar la base por una DHI es el movimiento de mayor retorno por menor esfuerzo que muestra la nota: 145 paquetes menos y cero hallazgos en el sistema operativo. Pero no resuelve lo que escribiste vos ni lo que tu build arrastra. Mi recomendación: reproducí la corrida con tu propia app, activá el SBOM de la etapa de build, actualizá las dependencias del pom.xml y dejá un gate de Trivy como condición de promoción, con o sin firma de Cosign. Mientras no haya réplicas independientes, tomá los porcentajes como orden de magnitud y no como promesa.

Fuentes

Te puede interesar...