Docker, Java y por qué esto cambia las reglas del juego
\n\nImagine: ha escrito una aplicación Java genial. Funciona en su MacBook, pero cuando se la entrega a un colega con Windows, todo se rompe. ¿Le suena familiar? ¿Y si el servidor es Linux? Ni siquiera hace falta adivinarlo: es la clásica historia de "en mi máquina funciona".
\n\nLa contenedorización con Docker resuelve este problema de una vez por todas. Usted empaqueta la aplicación junto con su entorno (JVM, bibliotecas, dependencias) en un contenedor ligero que se ejecuta igual en todas partes. Para Java esto es especialmente relevante, ya que estamos acostumbrados a "Write Once, Run Anywhere", pero en la práctica este lema a menudo falla debido a versiones de JDK, bibliotecas del sistema o configuraciones.
\n\nEn este artículo, analizaremos la contenedorización de aplicaciones Java con Docker de principio a fin. Aprenderá cómo escribir un Dockerfile, optimizar la imagen, usar Docker Compose y evitar los errores típicos. ¡Vamos allá!
\n\n¿Qué es Docker y por qué lo necesita un desarrollador Java?
\n\nDocker es una plataforma para empaquetar, distribuir y ejecutar aplicaciones en entornos aislados llamados contenedores. A diferencia de las máquinas virtuales, los contenedores comparten el núcleo del sistema operativo, pero tienen su propio espacio de archivos, red y procesos. Esto los hace ligeros (megabytes, no gigabytes) y rápidos (inicio en segundos).
\n\nPara un desarrollador Java, Docker proporciona tres superpoderes:
\n- \n
- Reproducibilidad — nunca más "en mi máquina funciona". Si el contenedor se inicia localmente, se iniciará en cualquier servidor. \n
- Aislamiento — cada aplicación vive en su propio mundo, sin interferir con otras. ¿Quiere dos aplicaciones Java con diferentes versiones de JDK? Fácil. \n
- Escalabilidad — los contenedores se clonan, balancean y orquestan fácilmente (Kubernetes, Docker Swarm). \n
Primer Dockerfile para una aplicación Java
\n\nEmpecemos con algo simple. Tenemos una aplicación Java estándar (por ejemplo, Spring Boot o simplemente Main.java). Queremos empaquetarla en una imagen Docker. La forma más sencilla es usar la imagen oficial de OpenJDK.
\n\nAquí hay un Dockerfile mínimo:
# Usamos la imagen oficial de OpenJDK 17\nFROM openjdk:17-jdk-alpine\n\n# Establecemos el directorio de trabajo\nWORKDIR /app\n\n# Copiamos el archivo JAR al contenedor\nCOPY target/my-app.jar /app/my-app.jar\n\n# Ejecutamos la aplicación\nCMD ["java", "-jar", "my-app.jar"]\n\nAhora construyamos la imagen y ejecutemos el contenedor:
\n\n# Construcción de la imagen\ndocker build -t my-java-app .\n\n# Ejecución del contenedor\ndocker run -p 8080:8080 my-java-app\n\nTodo funciona. Pero este es un mal ejemplo. ¿Por qué? Vamos a analizarlo.
\n\nProblema n.º 1: Tamaño de la imagen
\nLa imagen openjdk:17-jdk-alpine pesa alrededor de 150 MB. Esto es normal, pero si agrega todas las dependencias, el tamaño puede crecer a más de 500 MB. Para producción, esto es demasiado.
Problema n.º 2: Compilación y tiempo de ejecución en la misma imagen
\nCopiamos el JAR ya compilado, lo que significa que la compilación (mvn clean package) debe hacerse de antemano. Es mejor usar una compilación multi-etapa (multi-stage build) para separar la fase de compilación de la fase de ejecución.
\n\nOptimización: compilación multi-etapa (multi-stage build)
\n\nLa idea es simple: en la primera etapa, usamos una imagen con Maven y JDK, compilamos el proyecto, y en la segunda, solo JRE y el JAR compilado. Esto reduce la imagen final varias veces.
\n\n# Etapa de compilación\nFROM maven:3.8.5-openjdk-17 AS builder\nWORKDIR /build\nCOPY pom.xml .\nCOPY src ./src\nRUN mvn clean package -DskipTests\n\n# Etapa de ejecución\nFROM openjdk:17-jre-alpine\nWORKDIR /app\nCOPY --from=builder /build/target/my-app.jar /app/my-app.jar\nEXPOSE 8080\nCMD ["java", "-jar", "my-app.jar"]\n\n¿Qué ha cambiado?
\n- \n
- La primera etapa (
builder) contiene Maven y JDK — solo es necesaria para la compilación. \n - La segunda etapa usa una imagen JRE ligera (~50 MB \n