This can be read as well here
In this post, I will explain how you can create slim docker images by creating customized JREs using
Those commands are present on JDKs since Java 9, but actually being mature enough since Java 11, they bring a new way to create your customized JRE with only the modules that you need from Java, and then, creating smaller Docker images to run your Java applications (Mainly focused in Spring Boot applications), saving some good bucks in your Container Repository application (ECR, Artifactory, Docker Hub).
Assuming you already have a JDK installed in your machine, if not, i strongly suggest you to use sdkman.io and install Java 17 version (Which is the latest LTS as of the date of this post)
First of all, we need to get the list produced from
jdeps based in your jarfile from Spring Boot, for example:
$ jdeps --list-deps --ignore-missing-deps your-fat-jar.jar java.base java.logging
Well, that doesn't seems right, huh?
Spring Boot has a strategy by default to generate a fat jar with all the needed dependencies inside of it, and
jdeps currently can't get all the dependencies recursively inside the jars from the fat-jar.
So, what you can do is to extract all the contents from the fat-jar and run jdeps against every jar in the
Or, you can try with try and error, and see which ClassNotFoundException you will get from each JRE version
But, from some previous experience, we usually need those packages in our minimal JRE:
java.compiler java.logging java.sql java.rmi java.naming java.management java.instrument java.security.jgss java.net.http jdk.httpserver jdk.naming.dns
But my suggestion is to try yourself and see which packages you will really need in your slim JRE
You can use
jlink locally to create your customized JRE, the command is somehow like this:
jlink \ --module-path "$JAVA_HOME/jmods" \ --add-modules [the java modules your application needs joined by ,] \ --verbose \ --strip-debug \ --compress 2 \ --no-header-files \ --no-man-pages \ --output /opt/jre-minimal
Explaining each flag:
--module-path <path> Module path. If not specified, the JDKs jmods directory will be used, if it exists. If specified, but it does not contain the java.base module, the JDKs jmods directory will be added, if it exists. --verbose Enable verbose tracing --strip-debug Strip debug information --compress=<0|1|2> Enable compression of resources: Level 0: No compression Level 1: Constant string sharing Level 2: ZIP --no-header-files Exclude include header files --no-man-pages Exclude man pages --output <path> Location of output path
You can get those informations as well by running
jlinks --help in your command line tool.
Well, take this as an example, as i mentioned, it's using the OpenJDK 17 version (Latest LTS by the time this post was written)
# Build our minimal JRE using jlink FROM openjdk:17-oraclelinux8 as builder USER root RUN jlink \ --module-path "$JAVA_HOME/jmods" \ --add-modules java.compiler,java.sql,java.naming,java.management,java.instrument,java.rmi,java.desktop,jdk.internal.vm.compiler.management,java.xml.crypto,java.scripting,java.security.jgss,jdk.httpserver,java.net.http,jdk.naming.dns,jdk.crypto.cryptoki,jdk.unsupported \ --verbose \ --strip-debug \ --compress 2 \ --no-header-files \ --no-man-pages \ --output /opt/jre-minimal USER app # Now it is time for us to build our real image on top of an slim version of debian FROM bitnami/minideb:bullseye COPY --from=builder /opt/jre-minimal /opt/jre-minimal ENV JAVA_HOME=/opt/jre-minimal ENV PATH="$PATH:$JAVA_HOME/bin" VOLUME /tmp # Copy the JRE created in the last step into our $JAVA_HOME # For gradle # COPY build/libs/app-*.jar app.jar # For maven # COPY target/app-*.jar app.jar ENTRYPOINT ["java","-jar","/app.jar"]
But, how efficient is this?
For example, in a regular spring boot application, it saved 200Mb of storage for every image generated, from
349.98, assuming that you always have 10 versions of your application in your container registry, you can save 2 GB of storage space for each application!
You can check an example in this repo