diff --git a/native-image/micronaut-webserver/Dockerfile.distroless-base.mostly b/native-image/micronaut-webserver/Dockerfile.distroless-base.mostly index 811491c..7dd5a36 100644 --- a/native-image/micronaut-webserver/Dockerfile.distroless-base.mostly +++ b/native-image/micronaut-webserver/Dockerfile.distroless-base.mostly @@ -8,7 +8,7 @@ RUN ./mvnw --no-transfer-progress clean package -Dpackaging=native-image -Pmostl # RUN ./mvnw --no-transfer-progress clean package -Dpackaging=native-image -DbuildArgs="--static-nolibc,-Os,-o target/webserver.mostly-static" # Distroless Base - provides glibc -FROM gcr.io/distroless/base-debian12 +FROM gcr.io/distroless/base-debian13 COPY --from=nativebuild /webserver/target/webserver.mostly-static / EXPOSE 8000 ENTRYPOINT ["/webserver.mostly-static", "-b", "0.0.0.0", "-d", "/web"] \ No newline at end of file diff --git a/native-image/micronaut-webserver/Dockerfile.distroless-java-base-jar b/native-image/micronaut-webserver/Dockerfile.distroless-java-base-jar index 79998e6..e2ba709 100644 --- a/native-image/micronaut-webserver/Dockerfile.distroless-java-base-jar +++ b/native-image/micronaut-webserver/Dockerfile.distroless-java-base-jar @@ -4,7 +4,7 @@ WORKDIR /webserver RUN ./mvnw --no-transfer-progress clean package # Distoless Java 21 (Debian) -FROM gcr.io/distroless/java21-debian12 +FROM gcr.io/distroless/java25-debian13 COPY --from=build /webserver/target/webserver-0.1.jar webserver-0.1.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "webserver-0.1.jar"] \ No newline at end of file diff --git a/native-image/micronaut-webserver/Dockerfile.distroless-java-base.dynamic b/native-image/micronaut-webserver/Dockerfile.distroless-java-base.dynamic index 251d5df..2f85b37 100644 --- a/native-image/micronaut-webserver/Dockerfile.distroless-java-base.dynamic +++ b/native-image/micronaut-webserver/Dockerfile.distroless-java-base.dynamic @@ -5,7 +5,7 @@ WORKDIR /webserver RUN ./mvnw --no-transfer-progress clean package -Dpackaging=native-image # Distroless Java Base - provides glibc and other libraries needed by the JDK -FROM gcr.io/distroless/java-base-debian12 +FROM gcr.io/distroless/java-base-debian13 COPY --from=nativebuild /webserver/target/webserver / EXPOSE 8000 ENTRYPOINT ["/webserver", "-b", "0.0.0.0", "-d", "/web"] \ No newline at end of file diff --git a/native-image/micronaut-webserver/Dockerfile.distroless-java-base.dynamic-optimized b/native-image/micronaut-webserver/Dockerfile.distroless-java-base.dynamic-optimized index 9e474c9..b339e0b 100644 --- a/native-image/micronaut-webserver/Dockerfile.distroless-java-base.dynamic-optimized +++ b/native-image/micronaut-webserver/Dockerfile.distroless-java-base.dynamic-optimized @@ -8,7 +8,7 @@ RUN ./mvnw --no-transfer-progress clean package -Dpackaging=native-image -Pdynam # RUN ./mvnw --no-transfer-progress clean package -Dpackaging=native-image -DbuildArgs="-Os,-o target/webserver.dynamic-optimized" # Distroless Java Base-provides glibc and other libraries needed by the JDK -FROM gcr.io/distroless/java-base-debian12 +FROM gcr.io/distroless/java-base-debian13 COPY --from=nativebuild /webserver/target/webserver.dynamic-optimized / EXPOSE 8000 ENTRYPOINT ["/webserver.dynamic-optimized", "-b", "0.0.0.0", "-d", "/web"] \ No newline at end of file diff --git a/native-image/micronaut-webserver/Dockerfile.distroless-java-base.dynamic-skipflow b/native-image/micronaut-webserver/Dockerfile.distroless-java-base.dynamic-skipflow new file mode 100644 index 0000000..4852737 --- /dev/null +++ b/native-image/micronaut-webserver/Dockerfile.distroless-java-base.dynamic-skipflow @@ -0,0 +1,14 @@ +FROM container-registry.oracle.com/graalvm/native-image:25 AS nativebuild +COPY . /webserver +WORKDIR /webserver +# Build a dynamically linked native image with optimization for size +RUN ./mvnw --no-transfer-progress clean package -Dpackaging=native-image -Pdynamic-skipflow-optimized + +# Alternative way to pass Native Image build options with `-DbuildArgs` without using Maven profiles: +# RUN ./mvnw --no-transfer-progress clean package -Dpackaging=native-image -DbuildArgs="-Os,-H:+UnlockExperimentalVMOptions,-H:+TrackPrimitiveValues,-H:+UsePredicates,-o target/webserver.dynamic-skipflow" + +# Distroless Java Base-provides glibc and other libraries needed by the JDK +FROM gcr.io/distroless/java-base-debian13 +COPY --from=nativebuild /webserver/target/webserver.dynamic-skipflow / +EXPOSE 8000 +ENTRYPOINT ["/webserver.dynamic-skipflow", "-b", "0.0.0.0", "-d", "/web"] \ No newline at end of file diff --git a/native-image/micronaut-webserver/Dockerfile.distroless-java-base.jlink b/native-image/micronaut-webserver/Dockerfile.distroless-java-base.jlink index 6f7d3c6..e1a2be2 100644 --- a/native-image/micronaut-webserver/Dockerfile.distroless-java-base.jlink +++ b/native-image/micronaut-webserver/Dockerfile.distroless-java-base.jlink @@ -18,7 +18,7 @@ RUN CP=$(cat cp.txt) && \ --output jlink-jre # Distroless Java Base-provides glibc and other libraries needed by the JDK -FROM gcr.io/distroless/java-base-debian12 +FROM gcr.io/distroless/java-base-debian13 COPY --from=build /webserver/target/webserver-0.1.jar webserver-0.1.jar COPY --from=build /webserver/jlink-jre jlink-jre EXPOSE 8080 diff --git a/native-image/micronaut-webserver/Dockerfile.eclipse-temurin-jar b/native-image/micronaut-webserver/Dockerfile.eclispe-temurin-jar similarity index 100% rename from native-image/micronaut-webserver/Dockerfile.eclipse-temurin-jar rename to native-image/micronaut-webserver/Dockerfile.eclispe-temurin-jar diff --git a/native-image/micronaut-webserver/README.md b/native-image/micronaut-webserver/README.md index 359e620..0c32896 100644 --- a/native-image/micronaut-webserver/README.md +++ b/native-image/micronaut-webserver/README.md @@ -28,7 +28,7 @@ In this workshop you will: * x86 Linux * `musl` toolchain * Container runtime such as [Docker](https://www.docker.com/gettingstarted/), or [Rancher Desktop](https://docs.rancherdesktop.io/getting-started/installation/) installed and running. -* [GraalVM 25](https://www.graalvm.org/downloads/). We recommend using [SDKMAN!](https://sdkman.io/). (For other download options, see [GraalVM Downloads](https://www.graalvm.org/downloads/).) +* [Oracle GraalVM 25](https://www.graalvm.org/downloads/). We recommend using [SDKMAN!](https://sdkman.io/). (For other download options, see [GraalVM Downloads](https://www.graalvm.org/downloads/).) ```bash sdk install java 25-graal ``` @@ -53,7 +53,7 @@ In this workshop you will: ./build-jar-eclipse-temurin.sh ``` Once the script finishes, a container image _eclipse-temurin-jar_ should be available. - Check its size. It should be **472MB**. + Check its size. It should be **472MB**. ```bash docker images ``` @@ -67,7 +67,7 @@ It requires a container image with a JDK and runtime libraries. ### Explanation -The Dockerfile provided for this step pulls [container-registry.oracle.com/graalvm/jdk:24](https://docs.oracle.com/en/graalvm/jdk/24/docs/getting-started/container-images/) for the builder, and then `gcr.io/distroless/java21-debian12` for the runtime. +The Dockerfile provided for this step pulls [container-registry.oracle.com/graalvm/jdk:25](https://docs.oracle.com/en/graalvm/jdk/25/docs/getting-started/container-images/) for the builder, and then `gcr.io/distroless/java25-debian13` for the runtime. The entrypoint for this image is equivalent to `java -jar`, so only a path to a JAR file is specified in `CMD`. ### Action @@ -112,7 +112,7 @@ See how much reduction in size you can gain. Introduced in Java 11, it provides a way to make applications more space efficient and cloud-friendly. The script _build-jlink.sh_ that runs `docker build` using the _Dockerfile.distroless-java-base.jlink_. -The Dockerfile contains two stages: first it generates a `jlink` custom runtime on a full JDK (`container-registry.oracle.com/graalvm/jdk:24`); then copies the runtime image folder along with static assets into a distroless Java base image, and sets the entrypoint. +The Dockerfile contains two stages: first it generates a `jlink` custom runtime on a full JDK (`container-registry.oracle.com/graalvm/jdk:25`); then copies the runtime image folder along with static assets into a distroless Java base image, and sets the entrypoint. Distroless Java base image provides `glibc` and other libraries needed by the JDK, **but not a full-blown JDK**. The application does not have to be modular, but you need to figure out which modules the application depends on to be able to `jlink` it. @@ -238,7 +238,7 @@ In this step, you will build a fully dynamically linked native image **with the GraalVM Native Image provides the option `-Os` which optimizes the resulting native image for file size. `-Os` enables `-O2` optimizations except those that can increase code or executable size significantly. -Learn more about different optimization levels in the [Native Image documentation](https://www.graalvm.org/latest/reference-manual/native-image/optimizations-and-performance/#optimization-levels). +Learn more about different optimization levels in the [Native Image documentation](https://www.graalvm.org/jdk25/reference-manual/native-image/optimizations-and-performance/#optimization-levels). To configure the Native Image build and have more manual control over the process, GraalVM provides the [Native Build Tools](https://graalvm.github.io/native-build-tools/latest/index.html): Maven and Gradle plugins for building native images. @@ -315,7 +315,85 @@ No Java Runtime Environment (JRE) is required. The size of the container came down from **132MB** to **102MB**. The executable size decreased by **24MB** (from 86MB to 62MB) just by applying the file size optimization - with no change in behavior or startup time! -## **STEP 5**: Build a Size-Optimized Mostly Static Native Image and Run Inside a Container + +## **STEP 5**: (Optional) Build a Size-Optimized Native Image with SkipFlow and Run Inside a Container + +In this step, you will build another fully dynamically linked native image but with the **SkipFlow** and **file size** optimizations on. Then you run it inside a container. + +### Explanation + +As of Oracle GraalVM 25, more performance improvements are enabled by default. +One of which is [SkipFlow](https://www.graalvm.org/release-notes/JDK_25/#native-image)-an extension to the Native Image static analysis that tracks primitive values and evaluates branching conditions dynamically during the process. + +Note: The feature is enabled by default. With the previous releases, it could be controlled using these host options: `-H:+TrackPrimitiveValues` and `-H:+UsePredicates`. + +For this, we have added a separate Maven profile with a different name for the generated native executable: +```xml + + dynamic-skipflow-optimized + + + + org.graalvm.buildtools + native-maven-plugin + + webserver.dynamic-skipflow + + -Os + -H:+UnlockExperimentalVMOptions + -H:+TrackPrimitiveValues + -H:+UsePredicates + + + + + + +``` + +> An alternative way to pass build options to Native Image, without creating Maven profiles, is by using `-DbuildArgs`: +```bash +./mvnw package -Dpackaging=native-image -DbuildArgs="-Os,-H:+UnlockExperimentalVMOptions,-H:+TrackPrimitiveValues,-H:+UsePredicates,-o target/webserver.dynamic-skipflow" +``` + +The Dockerfile for this step, _Dockerfile.distroless-java-base.dynamic-skipflow_, is pretty much the same as before: running a native image build inside the builder container, and then copying it over to a distroless base container with just enough to run the application. +No Java Runtime Environment (JRE) is required. + +### Action + +1. Run the script to build a size-optimized native executable and package it into a container: + ```bash + ./build-dynamic-image-skipflow.sh + ``` + +2. Once the build completes, a container image _distroless-java-base.dynamic-optimized_ should be available. Run it, mapping the ports: + ```bash + docker run --rm -p8080:8080 webserver:distroless-java-base.dynamic-skipflow + ``` + The application is running from the native image inside a container. + The startup time has not changed. + +3. Open a browser and navigate to [localhost:8080/](http://localhost:8080/). You see the GraalVM documentation pages served. + +4. Return to the terminal and stop the running container by clicking CTRL+C. + +5. Check the size of this container image: + ```bash + docker images + ``` + The expected output is: + ```bash + REPOSITORY TAG IMAGE ID CREATED SIZE + webserver distroless-java-base.dynamic-skipflow 6caada87f616 8 minutes ago 101MB + webserver distroless-java-base.dynamic-optimized 5e16a58b1649 10 minutes ago 102MB + webserver distroless-java-base.dynamic d7c449b9373d 12 minutes ago 132MB + webserver distroless-java-base.jlink dde1eb772aa5 15 minutes ago 167MB + webserver distroless-java-base.jar e285476a8266 32 minutes ago 216MB + webserver eclispe-temurin-jar f6eef8d2aa40 33 minutes ago 472MB + ``` + The gain is tiny: the container size reduced only by 1MB, but depending on the application, **SkipFlow can provide up to a 4% reduction in binary size without any additional impact on build time**. + +## **STEP 6**: Build a Size-Optimized Mostly Static Native Image and Run Inside a Container In this step, you will build a **mostly static** native image, with the file size optimization on, and then package it into a container image that provides `glibc`, and run. @@ -555,12 +633,13 @@ Note that the website static pages add 44MB to the container images size. Static | Container | Size of a build artefact
(JAR, Jlink runtime, native executable) | Base image | Container | |----------------------------------------|-----------------------------------------------------------------------|------------|-----------| -| eclipse-temurin-jar | webserver-0.1.jar **24MB** | eclipse-temurin:25 201MB | 472MB | -| distroless-java-base.jar | webserver-0.1.jar **24MB** | java21-debian12 192MB | 216MB | -| distroless-java-base.jlink | jlink-jre custom runtime **68MB** | java-base-debian12 128MB | 167MB | -| distroless-java-base.dynamic | webserver.dynamic **86MB** | java-base-debian12 128MB | 132MB | -| distroless-java-base.dynamic-optimized | webserver.dynamic-optimized **62MB** | java-base-debian12 128MB | 102MB | -| distroless-base.mostly-static | webserver.mostly-static **62MB** | base-debian12 48.3MB | 89.7MB | +| eclispe-temurin-jar | webserver-0.1.jar **24MB** | eclipse-temurin:25 201MB | 472MB | +| distroless-java-base.jar | webserver-0.1.jar **24MB** | java25-debian13 192MB | 216MB | +| distroless-java-base.jlink | jlink-jre custom runtime **68MB** | java-base-debian13 128MB | 167MB | +| distroless-java-base.dynamic | webserver.dynamic **86MB** | java-base-debian13 128MB | 132MB | +| distroless-java-base.dynamic-optimized | webserver.dynamic-optimized **62MB** | java-base-debian13 128MB | 102MB | +| distroless-java-base.dynamic-skipflow | webserver.dynamic-skipflow **61MB** | java-base-debian13 128MB | 101MB | +| distroless-base.mostly-static | webserver.mostly-static **62MB** | base-debian13 48.3MB | 89.7MB | | scratch.static | webserver.static **62MB** | scratch 2MB | 69.2MB | | scratch.static-upx | webserver.scratch.static-upx **20MB** | scratch 2MB | 22.3MB | diff --git a/native-image/micronaut-webserver/build-all.sh b/native-image/micronaut-webserver/build-all.sh index 446bd19..c5efa30 100755 --- a/native-image/micronaut-webserver/build-all.sh +++ b/native-image/micronaut-webserver/build-all.sh @@ -1,7 +1,7 @@ #!/bin/sh -./build-jar-eclipse-temurin.sh ./build-jar-java-base.sh +./build-jar-eclipse-temurin.sh ./build-jlink.sh ./build-dynamic-image.sh ./build-dynamic-image-optimized.sh diff --git a/native-image/micronaut-webserver/build-jar-eclipse-temurin.sh b/native-image/micronaut-webserver/build-jar-eclipse-temurin.sh index 82dee3c..c85d5ae 100755 --- a/native-image/micronaut-webserver/build-jar-eclipse-temurin.sh +++ b/native-image/micronaut-webserver/build-jar-eclipse-temurin.sh @@ -1,4 +1,4 @@ #!/bin/sh -# Eclipse Temurin Java 25 -docker build --no-cache . -f Dockerfile.eclipse-temurin-jar -t webserver:eclipse-temurin-jar \ No newline at end of file +# Eclipse-temurin:25 +docker build --no-cache . -f Dockerfile.eclispe-temurin-jar -t webserver:eclipse-temurin-jar diff --git a/native-image/micronaut-webserver/build-jar-java-base.sh b/native-image/micronaut-webserver/build-jar-java-base.sh index d190b7c..0b696d8 100755 --- a/native-image/micronaut-webserver/build-jar-java-base.sh +++ b/native-image/micronaut-webserver/build-jar-java-base.sh @@ -1,4 +1,4 @@ #!/bin/sh -# Distroless Java 21 (Debian)-provides glibc and other libraries needed by the JDK +# Distroless Java 25 (Debian)-provides glibc and other libraries needed by the JDK docker build --no-cache . -f Dockerfile.distroless-java-base-jar -t webserver:distroless-java-base.jar \ No newline at end of file diff --git a/native-image/micronaut-webserver/pom.xml b/native-image/micronaut-webserver/pom.xml index 3a03d0e..05a72da 100644 --- a/native-image/micronaut-webserver/pom.xml +++ b/native-image/micronaut-webserver/pom.xml @@ -19,9 +19,9 @@ jar - 21 - 21 - 4.9.2 + 25 + 25 + 4.8.2 netty false com.example.webserver.Application diff --git a/native-image/micronaut-webserver/src/main/resources/static.zip b/native-image/micronaut-webserver/src/main/resources/static.zip index 6f620dd..de0fca1 100644 Binary files a/native-image/micronaut-webserver/src/main/resources/static.zip and b/native-image/micronaut-webserver/src/main/resources/static.zip differ diff --git a/native-image/spring-boot-webserver/Dockerfile.distroless-base.mostly b/native-image/spring-boot-webserver/Dockerfile.distroless-base.mostly index c59ffb3..a8624f1 100644 --- a/native-image/spring-boot-webserver/Dockerfile.distroless-base.mostly +++ b/native-image/spring-boot-webserver/Dockerfile.distroless-base.mostly @@ -5,7 +5,7 @@ WORKDIR /webserver RUN ./mvnw -Dmaven.test.skip=true -Pnative,mostly-static native:compile # Distroless Base - provides glibc -FROM gcr.io/distroless/base-debian12 +FROM gcr.io/distroless/base-debian13 COPY --from=nativebuild /webserver/target/webserver.mostly-static / EXPOSE 8000 ENTRYPOINT ["/webserver.mostly-static", "-b", "0.0.0.0", "-d", "/web"] \ No newline at end of file diff --git a/native-image/spring-boot-webserver/Dockerfile.distroless-java-base-jar b/native-image/spring-boot-webserver/Dockerfile.distroless-java-base-jar index 9e07b01..78e4011 100644 --- a/native-image/spring-boot-webserver/Dockerfile.distroless-java-base-jar +++ b/native-image/spring-boot-webserver/Dockerfile.distroless-java-base-jar @@ -3,9 +3,8 @@ COPY . /webserver WORKDIR /webserver RUN ./mvnw clean package -##### TODO: Upgrade to Distroless Java 25 (Debian) -# Distoless Java 21 (Debian) -FROM gcr.io/distroless/java21-debian12 +# Distoless Java 25 (Debian) +FROM gcr.io/distroless/java25-debian13 COPY --from=build /webserver/target/webserver-0.0.1-SNAPSHOT.jar webserver-0.0.1-SNAPSHOT.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "webserver-0.0.1-SNAPSHOT.jar"] \ No newline at end of file diff --git a/native-image/spring-boot-webserver/Dockerfile.distroless-java-base.dynamic b/native-image/spring-boot-webserver/Dockerfile.distroless-java-base.dynamic index 9eb0db1..52e1790 100644 --- a/native-image/spring-boot-webserver/Dockerfile.distroless-java-base.dynamic +++ b/native-image/spring-boot-webserver/Dockerfile.distroless-java-base.dynamic @@ -5,7 +5,7 @@ WORKDIR /webserver RUN ./mvnw -Dmaven.test.skip=true -Pnative native:compile # Distroless Java Base - provides glibc and other libraries needed by the JDK -FROM gcr.io/distroless/java-base-debian12 +FROM gcr.io/distroless/java-base-debian13 COPY --from=nativebuild /webserver/target/webserver.dynamic / EXPOSE 8000 ENTRYPOINT ["/webserver.dynamic", "-b", "0.0.0.0", "-d", "/web"] \ No newline at end of file diff --git a/native-image/spring-boot-webserver/Dockerfile.distroless-java-base.dynamic-optimized b/native-image/spring-boot-webserver/Dockerfile.distroless-java-base.dynamic-optimized index 08bb21b..013e40d 100644 --- a/native-image/spring-boot-webserver/Dockerfile.distroless-java-base.dynamic-optimized +++ b/native-image/spring-boot-webserver/Dockerfile.distroless-java-base.dynamic-optimized @@ -5,7 +5,7 @@ WORKDIR /webserver RUN ./mvnw -Dmaven.test.skip=true -Pnative,dynamic-size-optimized native:compile # Distroless Java Base-provides glibc and other libraries needed by the JDK -FROM gcr.io/distroless/java-base-debian12 +FROM gcr.io/distroless/java-base-debian13 COPY --from=nativebuild /webserver/target/webserver.dynamic-optimized / EXPOSE 8000 ENTRYPOINT ["/webserver.dynamic-optimized", "-b", "0.0.0.0", "-d", "/web"] \ No newline at end of file diff --git a/native-image/spring-boot-webserver/Dockerfile.distroless-java-base.dynamic-skipflow b/native-image/spring-boot-webserver/Dockerfile.distroless-java-base.dynamic-skipflow new file mode 100644 index 0000000..1eac0cb --- /dev/null +++ b/native-image/spring-boot-webserver/Dockerfile.distroless-java-base.dynamic-skipflow @@ -0,0 +1,11 @@ +FROM container-registry.oracle.com/graalvm/native-image:25 AS nativebuild +COPY . /webserver +WORKDIR /webserver +# Build a dynamically linked native image with optimization for size +RUN ./mvnw -Dmaven.test.skip=true -Pnative,dynamic-skipflow-optimized native:compile + +# Distroless Java Base-provides glibc and other libraries needed by the JDK +FROM gcr.io/distroless/java-base-debian13 +COPY --from=nativebuild /webserver/target/webserver.dynamic-skipflow / +EXPOSE 8000 +ENTRYPOINT ["/webserver.dynamic-skipflow", "-b", "0.0.0.0", "-d", "/web"] \ No newline at end of file diff --git a/native-image/spring-boot-webserver/Dockerfile.distroless-java-base.jlink b/native-image/spring-boot-webserver/Dockerfile.distroless-java-base.jlink index a091ee2..4727c1e 100644 --- a/native-image/spring-boot-webserver/Dockerfile.distroless-java-base.jlink +++ b/native-image/spring-boot-webserver/Dockerfile.distroless-java-base.jlink @@ -16,7 +16,7 @@ RUN jlink \ --output jlink-jre # Distroless Java Base-provides glibc and other libraries needed by the JDK -FROM gcr.io/distroless/java-base-debian12 +FROM gcr.io/distroless/java-base-debian13 COPY --from=build /webserver/target/webserver-0.0.1-SNAPSHOT.jar webserver-0.0.1-SNAPSHOT.jar COPY --from=build /webserver/jlink-jre jlink-jre EXPOSE 8080 diff --git a/native-image/spring-boot-webserver/Dockerfile.eclipse-temurin-jar b/native-image/spring-boot-webserver/Dockerfile.eclipse-temurin-jar index 7754357..4cfdfee 100644 --- a/native-image/spring-boot-webserver/Dockerfile.eclipse-temurin-jar +++ b/native-image/spring-boot-webserver/Dockerfile.eclipse-temurin-jar @@ -3,7 +3,7 @@ COPY . /webserver WORKDIR /webserver RUN ./mvnw clean package -# Eclipse Temurin Java 25 +# Distoless Java 25 (Debian) FROM eclipse-temurin:25 COPY --from=build /webserver/target/webserver-0.0.1-SNAPSHOT.jar webserver-0.0.1-SNAPSHOT.jar EXPOSE 8080 diff --git a/native-image/spring-boot-webserver/README.md b/native-image/spring-boot-webserver/README.md index 72dddb2..d9f966a 100644 --- a/native-image/spring-boot-webserver/README.md +++ b/native-image/spring-boot-webserver/README.md @@ -28,7 +28,7 @@ In this workshop you will: * x86 Linux * `musl` toolchain * Container runtime such as [Docker](https://www.docker.com/gettingstarted/), or [Rancher Desktop](https://docs.rancherdesktop.io/getting-started/installation/) installed and running. -* [GraalVM 25](https://www.graalvm.org/downloads/). We recommend using [SDKMAN!](https://sdkman.io/). (For other download options, see [GraalVM Downloads](https://www.graalvm.org/downloads/).) +* [Oracle GraalVM 25](https://www.graalvm.org/downloads/). We recommend using [SDKMAN!](https://sdkman.io/). (For other download options, see [GraalVM Downloads](https://www.graalvm.org/downloads/).) ```bash sdk install java 25-graal ``` @@ -67,7 +67,7 @@ It requires a container image with a full JDK and runtime libraries. ### Explanation -The Dockerfile provided for this step pulls [container-registry.oracle.com/graalvm/jdk:25](https://docs.oracle.com/en/graalvm/jdk/25/docs/getting-started/container-images/) for the builder, and then `gcr.io/distroless/java21-debian12` for the runtime. +The Dockerfile provided for this step pulls [container-registry.oracle.com/graalvm/jdk:25](https://docs.oracle.com/en/graalvm/jdk/25/docs/getting-started/container-images/) for the builder, and then `gcr.io/distroless/java25-debian13` for the runtime. The entrypoint for this image is equivalent to `java -jar`, so only a path to a JAR file is specified in `CMD`. ### Action @@ -285,7 +285,7 @@ In this step, you will build a fully dynamically linked native image **with the GraalVM Native Image provides the option `-Os` which optimizes the resulting native image for file size. `-Os` enables `-O2` optimizations except those that can increase code or executable size significantly. -Learn more in [the Native Image documentation](https://www.graalvm.org/latest/reference-manual/native-image/optimizations-and-performance/#optimization-levels). +Learn more in [the Native Image documentation](https://www.graalvm.org/jdk25/reference-manual/native-image/optimizations-and-performance/#optimization-levels). For that, a separate Maven profile is provided to differentiate this run from the default build, and giving a different name for the output file: ```xml @@ -348,7 +348,78 @@ The Dockerfile for this step, _Dockerfile.distroless-java-base.dynamic-optimized The size of the native executable decreased from **103MB** to **69MB** by applying the file size optimization! -## **STEP 6**: Build a Size-Optimized Mostly Static Native Image and Run Inside a Container +## **STEP 6**: (Optional) Build a Size-Optimized Native Image with SkipFlow and Run Inside a Container + +In this step, you will build another fully dynamically linked native image but with the **SkipFlow** and **file size** optimizations on. Then you run it inside a container. + +### Explanation + +As of Oracle GraalVM 25, more performance improvements are enabled by default. One of which is [SkipFlow](https://www.graalvm.org/release-notes/JDK_25/#native-image)-an extension to the Native Image static analysis that tracks primitive values and evaluates branching conditions dynamically during the process. + +Note: The feature is enabled by default. With the previous releases, it could be controlled using these host options: `-H:+TrackPrimitiveValues` and `-H:+UsePredicates`. + +For that, a separate Maven profile is provided, giving a different name for the output file: +```xml + + dynamic-skipflow-optimized + + + + org.graalvm.buildtools + native-maven-plugin + + webserver.dynamic-skipflow + + -Os + -H:+UnlockExperimentalVMOptions + -H:+TrackPrimitiveValues + -H:+UsePredicates + + + + + + +``` + +The Dockerfile for this step, _Dockerfile.distroless-java-base.dynamic-skipflow_, is pretty much the same as before: running a native image build inside the builder container, and then copying it over to a distroless base container with just enough to run the application. No Java Runtime Environment (JRE) is required. + +### Action + +1. Run the script to build a size-optimized native executable and package it into a container: + ```bash + ./build-dynamic-image-skipflow.sh + ``` + +2. Once the build completes, a container image _distroless-java-base.dynamic-optimized_ should be available. Run it, mapping the ports: + ```bash + docker run --rm -p8080:8080 webserver:distroless-java-base.dynamic-skipflow + ``` + + The application is running from the native image inside a container. + The startup time has not changed. + +3. Open a browser and navigate to [localhost:8080/](http://localhost:8080/). You see the GraalVM documentation pages served. + +4. Return to the terminal and stop the running container by clicking CTRL+C. + +5. Check the size of this container image: + ```bash + docker images + ``` + The expected output is: + ```bash + REPOSITORY TAG IMAGE ID CREATED SIZE + webserver distroless-java-base.dynamic-skipflow 7c748db34ef4 2 hours ago 103MB + webserver distroless-java-base.dynamic-optimized 5e16a58b1649 2 hours ago 105MB + webserver distroless-java-base.dynamic d7c449b9373d 2 hours ago 141MB + webserver distroless-java-base.jlink 191efb04958d 2 hours ago 178MB + webserver distroless-java-base.jar 846971900174 2 hours ago 225MB + webserver.buildpacks latest 615deed5b89c 45 years ago 142MB + ``` + The gain is tiny: the container size reduced only by 2MB, from 105MB to **103MB**, but depending on the application, **SkipFlow can provide up to a 4% reduction in binary size without any additional impact on build time**. + +## **STEP 7**: Build a Size-Optimized Mostly Static Native Image and Run Inside a Container In this step, you will build a **mostly static** native image, with the file size optimization on, and then package it into a container image that provides `glibc`, and run. @@ -414,9 +485,9 @@ A separate Maven profile exists for this step: ``` The size of the new _distroless-base.mostly-static_ container is **92.7MB**. - The reduction in size is related to the fact that a smaller base image was pulled: **gcr.io/distroless/base-debian12**. + The reduction in size is related to the fact that a smaller base image was pulled: **gcr.io/distroless/base-debian13**. [Distroless images](https://github.com/GoogleContainerTools/distroless) are very small, and the one used is only **48.3 MB**. - That's about 50% of the size of **java-base-debian12**(124 MB) used before, and 3 times less than **java21-debian12** (192 MB) containing a full JDK. + That's about 50% of the size of **java-base-debian13**(124 MB) used before, and 3 times less than **java25-debian13** (192 MB) containing a full JDK. The size of the mostly static native image has not changed, and is **69MB**. @@ -577,13 +648,14 @@ Sorted by size, it is clear that the fully static native image, compressed with | Container | Size of a build artefact
(JAR, Jlink runtime, native executable) | Base image | Container | |----------------------------------------|-----------------------------------------------------------------------|------------|-----------| -| eclipse-temurin-jar | webserver-0.0.1-SNAPSHOT.jar **32M** | eclipse-temurin:25 201MB | 481MB | -| distroless-java-base.jar | webserver-0.0.1-SNAPSHOT.jar **32M** | java21-debian12 192MB | 225MB | -| distroless-java-base.jlink | jlink-jre custom runtime **68MB** | java-base-debian12 128MB | 178MB | -| distroless-java-base.dynamic | webserver.dynamic **103MB** | java-base-debian12 128MB | 141MB | +| eclispe-temurin-jar | webserver-0.0.1-SNAPSHOT.jar **32M** | eclipse-temurin:25 201MB | 481MB | +| distroless-java-base.jar | webserver-0.0.1-SNAPSHOT.jar **32M** | java25-debian13 192MB | 225MB | +| distroless-java-base.jlink | jlink-jre custom runtime **68MB** | java-base-debian13 128MB | 178MB | +| distroless-java-base.dynamic | webserver.dynamic **103MB** | java-base-debian13 128MB | 141MB | | webserver.buildpacks:latest | | | 142MB | -| distroless-java-base.dynamic-optimized | webserver.dynamic-optimized **69MB** | java-base-debian12 128MB | 105MB | -| distroless-base.mostly-static | webserver.mostly-static **69MB** | base-debian12 48.3MB | 92.7MB | +| distroless-java-base.dynamic-optimized | webserver.dynamic-optimized **69MB** | java-base-debian13 128MB | 105MB | +| distroless-java-base.dynamic-skipflow | webserver.dynamic-skipflow **67MB** | java-base-debian13 128MB | 103MB | +| distroless-base.mostly-static | webserver.mostly-static **69MB** | base-debian13 48.3MB | 92.7MB | | scratch.static-alpine | webserver.static **69MB** | alpine:3 5MB | 80.5MB | | scratch.static | webserver.static **69MB** | scratch 2MB | 72.2MB | | scratch.static-upx | webserver.scratch.static-upx **19MB** | scratch 2MB | 19.1MB | diff --git a/native-image/spring-boot-webserver/build-jar-eclipse-temurin.sh b/native-image/spring-boot-webserver/build-jar-eclipse-temurin.sh index 82dee3c..4c44bb0 100755 --- a/native-image/spring-boot-webserver/build-jar-eclipse-temurin.sh +++ b/native-image/spring-boot-webserver/build-jar-eclipse-temurin.sh @@ -1,4 +1,4 @@ #!/bin/sh -# Eclipse Temurin Java 25 -docker build --no-cache . -f Dockerfile.eclipse-temurin-jar -t webserver:eclipse-temurin-jar \ No newline at end of file +# Eclipse-temurin:25 +docker build --no-cache . -f Dockerfile.eclipse-temurin-jar -t webserver:eclipse-temurin-jar diff --git a/native-image/spring-boot-webserver/build-jar-java-base.sh b/native-image/spring-boot-webserver/build-jar-java-base.sh index d190b7c..0b696d8 100755 --- a/native-image/spring-boot-webserver/build-jar-java-base.sh +++ b/native-image/spring-boot-webserver/build-jar-java-base.sh @@ -1,4 +1,4 @@ #!/bin/sh -# Distroless Java 21 (Debian)-provides glibc and other libraries needed by the JDK +# Distroless Java 25 (Debian)-provides glibc and other libraries needed by the JDK docker build --no-cache . -f Dockerfile.distroless-java-base-jar -t webserver:distroless-java-base.jar \ No newline at end of file diff --git a/native-image/spring-boot-webserver/pom.xml b/native-image/spring-boot-webserver/pom.xml index 51561a7..12bca3e 100644 --- a/native-image/spring-boot-webserver/pom.xml +++ b/native-image/spring-boot-webserver/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.6 + 4.0.1 com.example @@ -27,7 +27,7 @@ - 21 + 25 com.example.webserver.WebserverApplication 0.11.1 diff --git a/native-image/spring-boot-webserver/src/main/resources/static.zip b/native-image/spring-boot-webserver/src/main/resources/static.zip index 6f620dd..de0fca1 100644 Binary files a/native-image/spring-boot-webserver/src/main/resources/static.zip and b/native-image/spring-boot-webserver/src/main/resources/static.zip differ