Docker y Java: contenerización avanzada de aplicaciones

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Docker y Java: nivel avanzado de contenerización de aplicaciones

\n\n

La contenerización de aplicaciones Java se ha convertido en el estándar de facto en el desarrollo moderno. Sin embargo, el conocimiento básico de Docker (crear una imagen desde openjdk:11 y ejecutar un contenedor) ya no cumple con los requisitos de los entornos de producción. A nivel avanzado, es necesario considerar las particularidades del funcionamiento de la JVM dentro del contenedor, optimizar el tamaño de las imágenes, gestionar la memoria y configurar el monitoreo. En este artículo, analizaremos las técnicas clave que le ayudarán a crear imágenes Docker eficientes y seguras para aplicaciones Java.

\n\n

1. Compilación multi-etapa: minimización del tamaño de la imagen

\n\n

El enfoque clásico de "un Dockerfile, una imagen" a menudo conduce a la hinchazón del artefacto final. En la imagen se incluyen herramientas de compilación (Maven, Gradle, compilador) que no son necesarias en tiempo de ejecución. La solución es la compilación multi-etapa (multi-stage). Dividimos el proceso en fases: compilación (build) y ejecución (runtime).

\n\n

Ejemplo de Dockerfile para una aplicación Spring Boot usando Maven:

\n\n
# Etapa de compilación\nFROM maven:3.8.6-openjdk-17 AS builder\nWORKDIR /app\nCOPY pom.xml .\nRUN mvn dependency:go-offline -B\nCOPY src ./src\nRUN mvn clean package -DskipTests\n\n# Etapa de ejecución\nFROM eclipse-temurin:17-jre-alpine\nWORKDIR /app\nCOPY --from=builder /app/target/*.jar app.jar\nEXPOSE 8080\nENTRYPOINT ["java", "-jar", "app.jar"]
\n\n

Como resultado, la imagen final pesa ~150 MB en lugar de 600+ MB. Importante: use imágenes jre (no jdk) y distribuciones mínimas (Alpine, Slim). Para aplicaciones con Java 17+, puede considerar jlink — la creación de un JRE personalizado solo con los módulos necesarios.

\n\n

2. Configuración de la JVM para funcionar dentro del contenedor

\n\n

Durante mucho tiempo, la JVM no podía determinar correctamente los límites de memoria y CPU establecidos para el contenedor. A partir de Java 8u191 y Java 10, se agregó soporte para cgroups v1 (y con Java 15, también para cgroups v2). Sin embargo, no se debe confiar únicamente en la automatización.

\n\n

Parámetros clave para producción:

\n
    \n
  • -XX:+UseContainerSupport — habilita el soporte para contenedores (activo por defecto en Java 10+).
  • \n
  • -XX:MaxRAMPercentage=75.0 — limita el uso del heap al 75% de la memoria disponible del contenedor. Esto deja memoria para el metaspacio, la pila de hilos y los procesos del sistema.
  • \n
  • -XX:ActiveProcessorCount=2 — indicación explícita del número de procesadores disponibles, si la detección automática falla.
  • \n
\n\n

Ejemplo de comando ENTRYPOINT completo:

\n\n
ENTRYPOINT ["java", \\\n    "-XX:+UseContainerSupport", \\\n    "-XX:MaxRAMPercentage=75.0", \\\n    "-XX:+ExitOnOutOfMemoryError", \\\n    "-jar", "app.jar"]
\n\n

Importante: evite valores fijos de -Xmx y -Xms en entornos Docker. No se adaptan a los cambios en los límites del contenedor durante la replicación o el escalado.

\n\n

3. Optimización de capas y almacenamiento en caché de la compilación

\n\n

Cada instrucción en un Dockerfile crea una nueva capa. Docker almacena en caché las capas que no han cambiado. Para acelerar las compilaciones repetidas, coloque las instrucciones de las menos cambiantes a las más cambiantes.

\n\n

Estructura correcta del Dockerfile:

\n
    \n
  1. Instalación de dependencias del SO (apk add, apt-get).
  2. \n
  3. Copia de archivos de configuración del compilador (pom.xml, build.gradle).
  4. \n
  5. Descarga de dependencias (mvn dependency:go-offline).
  6. \n
  7. Copia del código fuente.
  8. \n
  9. Compilación y copia del artefacto.
  10. \n
\n\n

Adicionalmente, puede usar el almacenamiento en caché de capas de Docker en CI/CD. Por ejemplo, en GitHub Actions o GitLab CI, guarde el caché de Maven entre compilaciones:

\n\n
# Ejemplo para GitLab CI\ncache:\n  key: ${CI_COMMIT_REF_SLUG}\n  paths:\n    - .m2/repository/\n    - .gradle/caches/
\n\n

También considere el uso de

Blogs

Book Recommendations