# --- ETAPA 1: Compilación y Caché de Dependencias ---
FROM gradle:8-jdk17 AS build
WORKDIR /home/gradle/ipc
# 1. Copiar solo los archivos de configuración de Gradle
# Esto permite que Docker cachee las dependencias de internet
COPY build.gradle settings.gradle /home/gradle/ipc/
# 2. Descargar dependencias (truco para que no se descarguen en cada cambio de código)
RUN gradle dependencies --no-daemon || true
# 3. Copiar el código fuente original
COPY src /home/gradle/ipc/src
# 4. Limpiar y Compilar (Borra lo anterior dentro de la etapa y genera el nuevo .jar)
RUN gradle clean bootJar --no-daemon
# --- ETAPA 2: Imagen Final Ligera ---
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# Copiar el JAR generado en la etapa anterior
COPY --from=build /home/gradle/ipc/build/libs/*.jar app.jar
EXPOSE 15000
ENTRYPOINT ["java", "-jar", "app.jar"]
, piensa en cada instrucción FROM como si estuvieras encendiendo una computadora virtual nueva con un sistema operativo o un software principal ya instalado
Aquí “enciendes” la primera computadora. Su software principal es Gradle (con Java 17):
FROM gradle:8-jdk17 AS build
Todo lo que viene abajo (WORKDIR, COPY, RUN) se ejecuta dentro de esta computadora con Gradle
Usas esta máquina pesada porque tiene las herramientas necesarias para descargar las dependencias de internet y compilar tu código para generar el archivo final (.jar)
Aquí “apagas” la computadora anterior y enciendes una completamente nueva y vacía. Su software principal es solo el entorno de ejecución de Java (JRE), que es una versión superligera (Alpine) que solo sirve para ejecutar aplicaciones, no para compilarlas:
FROM eclipse-temurin:17-jre-alpine
A partir de esta línea, los nuevos WORKDIR, COPY, EXPOSE y ENTRYPOINT se aplican solo a esta segunda computadora
Con la línea COPY --from=build ..., metes la mano en la primera computadora (“build”), robas el archivo .jar que fabricaste, y lo metes en esta nueva computadora ligera
Si no usaras dos FROM, tu imagen final de Docker pesaría muchísimo (quizás 800 MB o más) porque incluiría todo el instalador de Gradle y herramientas de compilación que ya no necesitas para ejecutar la app. Al separarlo, la primera computadora se descarta y tu imagen final queda superligera (quizás unos 150 MB), lo cual es ideal, más rápido y más seguro para subir a producción
AS
Ese --from=build le dice a Docker: “No busques este archivo en mi computadora ni en la imagen actual de Alpine; ve a la etapa anterior (que etiquetamos como AS build en la primera línea) y saca el archivo de allá”
# 1. Aquí le pones el nombre "build" a la primera etapa
FROM gradle:8-jdk17 AS build
...
# 2. Aquí Gradle compila y guarda el archivo dentro de esa etapa "build"
RUN gradle clean bootJar --no-daemon
# --- ETAPA 2 ---
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# 3. Aquí viajas al pasado, entras a "build" y te traes el archivo .jar
COPY --from=build /home/gradle/ipc/build/libs/*.jar app.jar
Gradle genera el archivo ejecutable en la ruta:
/home/gradle/ipc/build/libs/tu-aplicacion-0.0.1.jar
Y ese es precisamente el archivo que luego copias con la línea:
COPY --from=build /home/gradle/ipc/build/libs/*.jar app.jar
¿Por qué se hace así?
Es una excelente práctica llamada Multi-stage build (construcción multi-etapa)
Si no lo hicieras así, tu imagen final de Docker tendría que incluir a Gradle, compiladores y herramientas que pesan cientos de megabytes y que solo necesitas una vez para compilar. Al usar --from=build, dejas todo el “basurero” del proceso de compilación atrás y te quedas únicamente con el archivo ejecutable (app.jar) montado sobre una base ultra ligera (alpine).
El resultado: una imagen final mucho más pequeña, rápida de descargar y más segura
puedo tener un .jar generado q dockefile no le va a dar importancia?
Al Dockerfile no le importa en lo más mínimo qué archivos tengas en tu carpeta local, a menos que uses explícitamente una instrucción para meterlos en la imagen.
Si usás un archivo de varias etapas donde Docker se encarga de compilar todo, el flujo es así:
# Etapa 1: Compilación
FROM gradle:8-jdk21 AS build
COPY --chown=gradle:gradle . /home/gradle/src
WORKDIR /home/gradle/src
RUN gradle bootJar --no-daemon
# Etapa 2: Imagen final de ejecución
FROM eclipse-temurin:21-jre-alpine
# Copia el .jar generado ADENTRO del contenedor de la etapa 1,
# ignorando completamente cualquier .jar que tengas en tu PC local.
COPY --from=build /home/gradle/src/build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
si tenés un .jar viejo en tu carpeta local, Docker ni se entera porque la compilación limpia ocurre dentro del entorno aislado del contenedor

Si ninguno cambia, usará el caché en el paso de las dependencias (RUN gradle dependencies...), que es exactamente lo que buscas
La regla de Docker: Cuando pasas múltiples archivos de origen a un COPY, el último argumento siempre debe ser un directorio y debe terminar en /. Al poner /home/gradle/ipc/, Docker entiende: “Toma estos dos archivos y suéltalos dentro de esta carpeta”
Docker exige que el destino final sea una carpeta (terminada en /). De lo contrario, intentará fusionar los archivos o te dará un error

cada FROM define un bloque con sus propios WORKDIR, RUN, COPY, ENTRYPOINT
–from=build llama o invoca a “build”, osea lo generado por el bloque 1, eso es para separar las cosas, 1er compilar, 2do agarrar lo compilado y ejecutarlo
cada bloque se ejecuta secuencialmente y cada vez que llamó a FROM es como que prendo y apago la PC y se ejecutan los comandos dockerfile

--no-daemon: Por defecto, Gradle inicia un proceso en segundo plano (un “demonio”) que se queda activo para que las siguientes compilaciones sean más rápidas. En Docker, esto no nos sirve de nada porque el contenedor de compilación se va a destruir en cuanto termine el proceso. Usar --no-daemon evita que se desperdicie memoria RAM innecesariamente manteniendo ese proceso abierto
chown
El parámetro --chown (que viene de change owner o “cambiar propietario”) es una opción que puedes agregarle a las instrucciones COPY o ADD en un Dockerfile
Sirve para cambiar el dueño y el grupo de los archivos en el preciso momento en que los estás copiando dentro de la imagen.
Por defecto, si no usas --chown, Docker copia todos los archivos con permisos de root (el superusuario administrador)
ejemplo:
COPY --from=build --chown=gradle:gradle /home/gradle/ipc/build/libs/*.jar app.jar
En este caso, le estás diciendo a Docker: “Trae el .jar de la etapa de compilación, pero no lo guardes como root. Haz que el usuario gradle y el grupo gradle sean los dueños de este archivo”
separar ENTRYPOINT [“java”, “-jar”, “app.jar”] con un entrypoint y un cmd?
ENTRYPOINT ["java", "-jar"]
CMD ["app.jar"]
En Docker, cuando combinas ambas instrucciones utilizando el formato de lista (llamado exec form, con los corchetes []
Docker los junta al arrancar el contenedor y termina ejecutando exactamente el mismo comando que tenías antes: java -jar app.jar
dejar el nombre del archivo en el CMD te da la libertad de pasarle opciones o cambiar el archivo de arranque en el último segundo
¿Cuál es la ventaja de separarlos? (El verdadero beneficio)
La gran ventaja es la flexibilidad. El ENTRYPOINT define el comando fijo que siempre se va a ejecutar
Si usas tu comando original (todo en ENTRYPOINT): Es difícil cambiar el comportamiento al vuelo porque el comando está “esculpido en piedra”
COPY build.gradle settings.gradle /home/gradle/ipc/
COPY src /home/gradle/ipc/src
por que hay 2 COPY en lugar de uno?
Esa es una de las mejores tácticas para optimizar Dockerfiles, y la razón se resume en una sola palabra: caching (la memoria caché de Docker).
Docker construye las imágenes capa por capa. Si un paso y todos los anteriores no han cambiado, Docker no vuelve a ejecutar ese comando; simplemente reutiliza el resultado guardado (la capa en caché).
Al separar la copia en dos partes, estás aprovechando esto estratégicamente:
1. La primera capa: Las dependencias (build.gradle, settings.gradle)
Las dependencias de tu proyecto cambian con muy poca frecuencia.
Docker descarga todas las librerías de internet y las guarda en una capa de caché
El código fuente de tu aplicación cambia constantemente (cada vez que arreglas un bug o agregas una función)
¿Qué pasaría si usaras un solo COPY . .?
Si unieras todo en un solo comando como COPY . /home/gradle/ipc/, pasaría lo siguiente:
- Cada vez que modifiques una sola línea de código en tu carpeta
src, Docker detectará que “algo” cambió en los archivos copiados. - Al cambiar el contenido del
COPY, se invalida la caché de esa capa y de todas las que siguen. - Como resultado, Gradle tendría que volver a descargar todas las dependencias y librerías de internet en cada compilación. Tu proceso de
docker buildpasaría de tardar segundos a tardar varios minutos.
Separar los comandos asegura que Docker solo descargue las librerías cuando realmente agregues o cambies una dependencia en tu build.gradle, manteniendo tus compilaciones diarias sumamente rápidas