How to create slim Spring Boot docker images
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).
But, how do we start?
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
Creating my Java 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.
But then, how do I create my slim Spring Boot application docker image
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 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 debian image FROM bitnami/minideb:bullseye # Copy the JRE created in the last step into our $JAVA_HOME COPY --from=builder /opt/jre-minimal $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!