CodeGym /Cursos /Docker for Spring /jarmode=tools y capas de Spring Boot

jarmode=tools y capas de Spring Boot

Docker for Spring
Nivel 8, Lección 1
Disponible

1. ¿Qué es jarmode=tools?

Si alguna vez has pensado: «¿Por qué no puedo simplemente tomar el jar y ejecutarlo?», entonces felicidades, eres una persona normal. Pero la construcción Docker ama cuando sabemos no solo ejecutar, sino también descomponer el artefacto en partes. jarmode=tools es como el «modo de servicio» de un automóvil: el motor es el mismo, pero en lugar de conducir, abrimos el capó y hacemos mantenimiento.

Spring Boot puede empaquetar la aplicación de modo que dentro del jar aparezca información sobre las capas (layers index), y junto con esto se agrega soporte para un modo especial de jar mode — tools. Esto permite ejecutar el jar no como «levantar un servidor en un puerto», sino como «ejecutar un comando de mantenimiento»: mostrar las capas o extraerlas al sistema de archivos. Esto es precisamente lo que hace jarmode=tools, y se considera la forma canónica según la documentación actual de Spring Boot 4.0.x.

Es importante fijar la idea de inmediato (después ahorra muchos nervios): jarmode=tools no es algo que haya inventado Docker. Es una ejecución normal de un proceso Java con una propiedad del sistema -D.... Hay exactamente la misma magia que en la frase «ejecuta Java con un parámetro».

2. tools — modo jar, no utilidad

Psicológicamente, para un principiante es más fácil imaginar una «utilidad separada» que un «jar que puede tener diferentes modos». Por lo tanto, a menudo surge la expectativa de que tools es un programa externo como gradle o docker. En realidad, es todo lo contrario: el jar es el mismo, y la forma de ejecutarlo cambia gracias al parámetro JVM -Djarmode=tools.

Técnicamente, se ve así: ejecutamos java -jar ..., pero añadimos la propiedad del sistema jarmode. El Boot loader (ese cargador que normalmente inicia la aplicación) ve que no debe ejecutar tu main(), sino realizar un comando de servicio. Como resultado, el jar comienza a comportarse como una mini-CLI. La documentación muestra directamente esta ejecución como la entrada básica al modo tools:

java -Djarmode=tools -jar my-app.jar

Y sí, es uno de esos raros casos en los que «ejecuta el jar sin argumentos» es un comando útil. Porque muestra una sugerencia de qué comandos están disponibles.

También es útil no confundir dos cosas que parecen similares: jarmode=tools y los «perfiles de Spring» (spring.profiles.active). Los perfiles afectan el comportamiento de la aplicación al iniciarse. jarmode=tools afecta a si la aplicación se inicia o no. En modo tools, el servidor no se levanta, por lo que ningún server.port tiene sentido aquí.

3. Preparación: el jar correcto

Es muy fácil caer en la trampa de «bueno, solo voy a ver las capas». Java, por supuesto, no se opone, pero las capas solo aparecerán si el jar fue construido como un Spring Boot executable jar y contiene un índice de capas. En el curso, trabajamos con el proyecto transversal Container-Ready Catalog Service, y su artefacto base para Docker es el resultado de bootJar.

En un proyecto Gradle, normalmente se ve así (lo muestro en una forma lo más «copiable» posible):

# 1) Construimos específicamente bootJar para obtener un Spring Boot jar ejecutable con capas
./gradlew clean bootJar

# 2) Verificamos que el jar realmente apareció donde esperamos
ls -la build/libs

# verá algo como:
# docker-java-catalog-service-0.0.1-SNAPSHOT.jar

Si estás en Windows y ls no es lo tuyo, no importa: el significado es el mismo: el jar está en build/libs. En la documentación de Spring Boot incluso recuerdan que el ejemplo con target/*.jar debe reemplazarse por build/libs/*.jar si estás en el mundo de Gradle.

El nombre real del bootJar en disco puede tener versión, y eso es normal. Para no tener que ajustarnos a la versión, mantendremos una regla simple: afuera tomamos el archivo real de build/libs, y dentro de las etapas de Docker lo llamamos directamente application.jar. Esto es un alias local para los comandos extract, no un nuevo «nombre oficial» del artefacto del proyecto. Por lo tanto, en los comandos shell de abajo aparecerá build/libs/<su-bootJar>.jar, y en el Dockerfile, un nombre estable application.jar.

4. Ejecución del modo tools y sugerencia

Tenemos un jar. Ahora el ejercicio más útil para el cerebro es ejecutar tools sin comando y ver qué puede hacer. Este es exactamente el caso en el que «leer la ayuda» no es una molestia, sino un ahorro de tiempo.

El comando es:

# Ejecutamos el jar en modo tools especial (la aplicación NO se inicia)
java -Djarmode=tools -jar build/libs/<su-bootJar>.jar

# Sugerencia: si todo está bien, verá el uso y la lista de comandos (extract/list-layers/help)

La documentación de Spring Boot 4.0.3 muestra que en respuesta verá el uso y la lista de comandos disponibles. Hay al menos tres: extract, list-layers, help.

El significado de cada uno es simple:

list-layers — ver qué capas hay en tu jar y cómo se llaman. Es una «inspección», no extrae nada.

extract — descomponer el jar en directorios por capas, para que luego esos directorios se puedan copiar con COPY individuales en el Dockerfile (y así obtener capas Docker más precisas). La documentación dice directamente que extract es necesario para «split the application into layers» para el Dockerfile.

help — ayuda sobre un comando específico (útil cuando olvidas los argumentos).

Y aquí aparece un hábito importante de un profesional: antes de escribir un Dockerfile «a ciegas», primero leemos qué comandos están disponibles y cómo se llaman. Esto reduce enormemente la probabilidad de que empieces a buscar en Google «por qué no funciona layertools», cuando en realidad estás en Boot 4.

5. list-layers: primero ver los nombres de las capas, luego escribir el Dockerfile

Cuando un estudiante ve por primera vez un Dockerfile con capas, suele tener dos reacciones. La primera: «Oh, ¿se podía hacer así?». La segunda: «Todavía no entiendo de dónde salen esas carpetas extrañas». El comando list-layers es precisamente la respuesta a la segunda reacción: no crees ciegamente en el Dockerfile, primero preguntas las capas al jar.

Se ejecuta así:

# El comando no extrae nada: solo imprime los nombres de las capas del índice de capas
java -Djarmode=tools -jar build/libs/<su-bootJar>.jar list-layers

Lo que obtienes como salida es una lista de nombres de capas que el jar está listo para extraer. En una aplicación Spring Boot típica, verás un conjunto familiar como dependencies, spring-boot-loader, snapshot-dependencies, application. Lo clave aquí es otra cosa: list-layers te da cadenas exactas que luego aparecerán en archivos y rutas.

Un punto meticuloso pero muy práctico: es mejor tratar los nombres de las capas como una «API». Si el comando mostró spring-boot-loader, entonces como resultado de la extracción obtendrás un directorio spring-boot-loader/. No loader/, no boot-loader/, sino exactamente lo que dijo la herramienta. Cuanto menos «adivines», menos depurarás.

Otro punto que suele sorprender gratamente: list-layers es una forma rápida de verificar que tu jar es realmente «layer-aware». Si por alguna razón desactivaste el layering durante la construcción, aquí todo se aclarará antes de que escribas un Dockerfile de 40 líneas.

6. extract: descomponemos el jar en capas

Ahora pasamos de «ver» a «hacer». El comando extract es el caballo de batalla del enfoque de capas. Toma tu jar y descompone su contenido en un directorio que especifiques, de modo que cada capa quede en una carpeta separada. La documentación de Spring Boot muestra directamente la forma canónica del comando para Dockerfile: extract --layers --destination layers.

Aquí tienes una variante básica para ejecución local (sin Dockerfile, solo para entender qué sucede):

# Limpiamos el resultado anterior para no mezclar archivos y no obtener archivos "mezclados"
rm -rf layers
mkdir -p layers

# Extraemos el jar por capas en un directorio explícitamente especificado
java -Djarmode=tools -jar build/libs/<su-bootJar>.jar extract \
  --layers \
  --destination layers

Observa dos banderas que no son «cosméticas».

--destination layers hace que el resultado sea predecible. En el proyecto educativo y en el Dockerfile no queremos «se descomprimió en alguna parte, no sé dónde». Queremos una ruta exacta que sea cómoda para copiar después. La documentación también enfatiza --destination.

--layers dice: «descompón no de cualquier manera, sino por capas». Es decir, no es simplemente «unzip jar», sino «respeta el índice de capas».

Después de ejecutar el comando, verás que dentro de layers/ han aparecido subdirectorios de capas. Si prefieres ver con los ojos, puedes hacer:

# Comprobación rápida: si aparecieron los directorios de capas
ls -la layers

# esperadamente verás carpetas como:
# dependencies/
# spring-boot-loader/
# snapshot-dependencies/
# application/

Y ahora ocurre un cambio importante en la percepción: tu jar deja de ser «un solo archivo» desde el punto de vista de la construcción Docker. Se convierte en un conjunto de directorios que se pueden copiar con comandos COPY individuales, convirtiendo cada uno en una capa separada de la imagen.

Si representamos esto con un diagrama (sin intentar dibujar una obra maestra), quedaría algo así:

flowchart TD
  %% A la izquierda un jar, a la derecha — directorios por capas después de extract
  A["bootJar: un archivo .jar"] --> B["java -Djarmode=tools -jar ... list-layers"]
  B --> C["nombres de las capas (lista)"]
  A --> D["java -Djarmode=tools -jar ... extract --layers --destination layers"]
  D --> E["layers/dependencies"]
  D --> F["layers/spring-boot-loader"]
  D --> G["layers/snapshot-dependencies"]
  D --> H["layers/application"]

Este diagrama es bueno porque no incluye Docker. Porque ahora estamos aprendiendo precisamente a «obtener los directorios correctos», no a «construir la imagen final». Pero esos directorios son exactamente lo que Docker luego usará como «bloques de construcción».

7. ¿Qué sucede después de extract y para qué sirve aquí application.jar?

En este punto, los principiantes suelen tener un mini-pánico: «Espera, ya vimos application.jar. ¿Ese es el artefacto final en tiempo de ejecución?». Es importante distinguir dos roles.

En la etapa extract, application.jar es simplemente un nombre local conveniente para el bootJar original que trajimos de build/libs. Es necesario para que los comandos list-layers y extract sean cortos y no dependan de la versión en el nombre del archivo.

Después de extract, el resultado principal ya no es el archivo jar, sino los directorios de capas: layers/dependencies, layers/spring-boot-loader, layers/snapshot-dependencies, layers/application. Luego, estos se copian en tiempo de ejecución con COPY individuales, para que Docker obtenga varias capas independientes en lugar de un solo COPY app.jar.

Por lo tanto, la ruta manual final de capas normalmente no termina con un nuevo java -jar application.jar, sino con la ejecución a través de org.springframework.boot.loader.launch.JarLauncher. Esta es una distinción importante: application.jar vive como un artefacto de entrada conveniente para la etapa extract, y el runtime ya trabaja con la estructura exploded de la aplicación.

Si resumimos esto en una cadena corta, queda así:

- build/libs/<su-bootJar>.jar — el jar ejecutable real después de bootJar;
- application.jar — su nombre estable dentro de la etapa extract;
- layers/... — el resultado de extract, que Docker copia por partes;
- JarLauncher — la forma de ejecutar la estructura exploded ya ensamblada en runtime.

Precisamente esta cadena convierte el layering de «ver la lista de capas» en una ruta normal de construcción Docker.

8. Relación con el Dockerfile

En este punto, dan muchas ganas de armar todo el Dockerfile con capas de inmediato: las manos pican, el Dockerfile llama y COPY pide ser inteligente. Pero primero es importante mantener dos pilares: ver los nombres de las capas y aprender a extraerlas. Sin esto, el fragmento de runtime sigue siendo solo un conjunto de líneas.

Sin embargo, es útil saber cómo se ve esto en forma mínima en el Dockerfile, porque el comando extract generalmente se ejecutará durante docker build. La documentación de Spring Boot muestra un patrón multi-etapa, donde en la etapa builder se hace extract y luego en la etapa runtime se copian los directorios de capas por separado.

Mini-fragmento (solo como «esqueleto», sin todo lo demás):

FROM bellsoft/liberica-openjre-debian:25-cds AS extract

# En esta etapa solo "descomprimimos por capas", no ejecutamos la aplicación
WORKDIR /builder

# La plantilla de ruta es conveniente en el proyecto educativo: la versión del jar cambia, pero el Dockerfile no
ARG JAR_FILE=build/libs/*.jar

# Colocamos el jar con un nombre estable para que el comando RUN sea predecible
COPY ${JAR_FILE} application.jar

# Extraemos el contenido por capas en la carpeta layers (luego lo copiaremos por capas con COPY)
RUN java -Djarmode=tools -jar application.jar extract --layers --destination layers

Aquí hay tres ideas fundamentales que ya deberías reconocer de los días anteriores del curso. En primer lugar, es una etapa separada, es decir, no «ensuciamos» la imagen de runtime con lógica de construcción. En segundo lugar, el jar se renombra a un nombre estable application.jar para que los comandos sean cortos y no dependan de la versión. En tercer lugar, --destination establece una ruta estable, y el Dockerfile se vuelve legible.

El nombre del directorio aquí no es decorativo: la ruta estable es importante. Luego, el contenedor runtime toma el contenido de layers/... con COPY individuales y se inicia a través de JarLauncher, porque después de la extracción, el resultado principal son los directorios de capas, no el archivo original application.jar.

Aquí es importante no memorizar el Dockerfile, sino entender qué hace exactamente jarmode=tools dentro de la etapa extract.

9. Chuleta de comandos

Después de toda esta teoría, uno quiere tener algo así como una «chuleta, pero sin vergüenza». La tabla de abajo es justo eso: mínimo de comandos, máximo de significado.

Acción Comando Qué obtenemos Para qué en la imagen de capas
Entrar en modo tools y ver la ayuda java -Djarmode=tools -jar app.jar lista de comandos (extract, list-layers, help) entender rápidamente que el jar soporta tools y cómo usarlo
Ver los nombres de las capas ... list-layers lista de nombres de capas para no «adivinar» las carpetas en el Dockerfile, sino trabajar con los hechos
Extraer capas a un directorio ... extract --layers --destination layers carpetas layers/<layer>/... para copiar capas con COPY individuales y usar mejor la caché de Docker

Si puedes reproducir estas tres líneas (y explicar qué hacen), ya estás «en el tema» y listo para el siguiente paso de ingeniería: construir un Dockerfile con capas no a ciegas, sino con comprensión.

10. Errores típicos al trabajar con jarmode=tools

Error n.º 1: ejecutar tools antes de que el jar esté construido.
En la cabeza suena lógico: «solo voy a mirar». En la práctica, java -Djarmode=tools -jar build/libs/*.jar no encontrará el archivo (o encontrará uno incorrecto) y obtendrás un error del tipo «Unable to access jarfile». No se arregla con magia, sino con disciplina normal: primero ./gradlew bootJar, luego tools.

Error n.º 2: confundir la ruta de Maven target/*.jar con la ruta de Gradle build/libs/*.jar.
Este error es particularmente engañoso porque puedes copiar y pegar el ejemplo de un artículo, y parece «cercano a la verdad». En la documentación de Spring Boot se dice claramente que target/*.jar debe reemplazarse por build/libs/*.jar si usas Gradle. Si no lo reemplazas, Docker build buscará honestamente el jar donde no existe.

Error n.º 3: olvidar que list-layers no extrae nada.
A veces se espera que después de list-layers ya aparezcan carpetas. No aparecerán. list-layers es una inspección, una lectura del «mapa de capas». Las carpetas solo las crea extract. Una buena comprobación: después de list-layers, casi nada debería haber cambiado en el sistema de archivos.

Error n.º 4: no especificar --destination y luego buscar archivos «en algún lugar del contenedor».
Este es un problema clásico no solo del modo tools, sino de cualquier paso de construcción: si la ruta no es estable, no puedes escribir correctamente COPY --from=.... Por lo tanto, --destination layers no es un adorno, sino una forma de hacer que el Dockerfile sea legible y predecible. La documentación usa precisamente esa forma.

Error n.º 5: interpretar jarmode=tools como una configuración de tiempo de ejecución de la aplicación.
A veces se intenta «ejecutar el contenedor en modo tools», esperando que esto acelere o mejore el servicio. No: el modo tools es necesario en la etapa de construcción de la imagen y preparación de capas. En tiempo de ejecución, el contenedor debe ejecutar la aplicación, no desempaquetarse a sí mismo (esto suena como una escena de una película de terror sobre un jar que se come a sí mismo).

1
Tarea
Docker for Spring, nivel 8, lección 1
Bloqueada
Listado de capas mediante jarmode=tools
Listado de capas mediante jarmode=tools
1
Tarea
Docker for Spring, nivel 8, lección 1
Bloqueada
Extracción de capas en el directorio extracted
Extracción de capas en el directorio extracted
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION