Docker y Kotlin: contenerización de aplicaciones a nivel avanzado
\n\nLa contenerización se ha convertido en una parte integral del desarrollo moderno, y Kotlin no es una excepción. Si ya dominas los Dockerfile básicos y la ejecución de archivos JAR simples, es hora de pasar al siguiente nivel. En este artículo, exploraremos técnicas avanzadas de contenerización para aplicaciones Kotlin: compilación multi-etapa (multi-stage builds), optimización del tamaño de las imágenes, configuración para los frameworks Ktor y Spring Boot, así como el trabajo con Docker Compose en entornos de producción.
\n\n1. Compilación multi-etapa de aplicaciones Kotlin
\n\nUno de los principales problemas al contenerizar aplicaciones JVM es el tamaño de la imagen final. Una imagen estándar de OpenJDK puede pesar más de 400 MB. La compilación multi-etapa permite separar el entorno de compilación del entorno de ejecución, utilizando una imagen de runtime mínima.
\n\nEjemplo para un proyecto Gradle en Kotlin
\n\n# Primera etapa: compilación\nFROM gradle:8.5-jdk21 AS builder\nWORKDIR /app\nCOPY build.gradle.kts settings.gradle.kts ./\nCOPY src ./src\nRUN gradle build --no-daemon -x test\n\n# Segunda etapa: ejecución\nFROM eclipse-temurin:21-jre-alpine\nWORKDIR /app\nCOPY --from=builder /app/build/libs/app.jar app.jar\nEXPOSE 8080\nENTRYPOINT ["java", "-jar", "app.jar"]\n\nEl uso de eclipse-temurin:21-jre-alpine en lugar de la imagen completa del JDK reduce el tamaño de 400+ MB a ~120 MB. Para aplicaciones Kotlin con Ktor o Spring Boot, esto es más que suficiente.
Optimización del almacenamiento en caché de capas
\n\nPara acelerar las compilaciones repetidas, copia las dependencias por separado del código fuente:
\n\nFROM gradle:8.5-jdk21 AS builder\nWORKDIR /app\n# Primero las dependencias (se almacenan en caché si no cambian)\nCOPY build.gradle.kts settings.gradle.kts ./\nRUN gradle dependencies --no-daemon\n# Luego el código fuente\nCOPY src ./src\nRUN gradle build --no-daemon -x test\n\nEste truco es especialmente útil en CI/CD, donde cada segundo cuenta.
\n\n2. Optimización de imágenes para aplicaciones Kotlin
\n\nAdemás de elegir la imagen base, existen técnicas adicionales para reducir el tamaño y mejorar la seguridad.
\n\nUso de JLink para crear una JRE personalizada
\n\nSi tu aplicación solo utiliza una parte de los módulos del JDK, puedes crear una JRE mínima:
\n\n# En la etapa builder\nRUN jlink --module-path /opt/java/openjdk/jmods \\\n --add-modules java.base,java.logging,java.naming,java.sql \\\n --output /custom-jre \\\n --strip-debug --no-man-pages --no-header-files\n\n# En la etapa runtime\nFROM alpine:3.19\nCOPY --from=builder /custom-jre /opt/jre\nCOPY --from=builder /app/build/libs/app.jar /app/app.jar\nENV PATH="/opt/jre/bin:${PATH}"\nEXPOSE 8080\nCMD ["java", "-jar", "/app/app.jar"]\n\nEl tamaño de la imagen puede reducirse a 50-70 MB.
\n\nArquitectura multicapa con Spring Boot
\n\nSpring Boot 3+ admite la división del fat JAR en capas. Úsalo en el Dockerfile:
\n\nFROM eclipse-temurin:21-jre-alpine AS builder\nWORKDIR /app\nCOPY build/libs/*.jar app.jar\nRUN java -Djarmode=layertools -jar app.jar extract\n\nFROM eclipse-temurin:21-jre-alpine\nWORKDIR /app\nCOPY --from=builder /app/dependencies/ ./\nCOPY --from=builder /app/spring-boot-loader/ ./\nCOPY --from=builder /app/snapshot-dependencies/ ./\nCOPY --from=builder /app/application/ ./\nENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]\n\nAhora, al modificar el código, solo se reconstruye la capa application, mientras que las dependencias y el cargador de Spring Boot permanecen en caché.
3. Contenerización de aplicaciones Ktor
\n\nKtor es un framework ligero para crear servidores asíncronos en Kotlin. Su contenerización tiene sus propias particularidades.
\n\nDockerfile para Ktor con Netty
\n\nFROM gradle:8.5-jdk21 AS builder\nWORKDIR /app\nCOPY build.gradle.kts settings.gradle.kts ./\nRUN gradle dependencies --