Spring Boot container images: to JLink or not
Tom Wetjens
I was experimenting with building a container image for my Spring Boot application (Spring Boot version 3.5.7, Java version 25). And I was wondering which approach would
be better: use the Maven plugin with spring-boot:build-image to build the image, or use a custom Dockerfile with jlink.
mvn spring-boot:build-image
This results in a container image of 368 MB (using Spring Boot Maven plugin).
If we inspect the layers of the container image that the Spring Boot Maven plugin built:
$ docker history app:0.0.1-SNAPSHOT
IMAGE CREATED CREATED BY SIZE COMMENT
28ebba89c902 N/A Buildpacks Process Types 69B
<missing> N/A Buildpacks Launcher Config 1.95kB
<missing> N/A Buildpacks Application Launcher 2.83MB
<missing> N/A Application Slice: 5 0B
<missing> N/A Application Slice: 4 698kB
<missing> N/A Application Slice: 3 0B
<missing> N/A Application Slice: 2 400kB
<missing> N/A Application Slice: 1 52.6MB
<missing> N/A Software Bill-of-Materials 631kB
<missing> N/A Layer: 'web-application-type', Created by bu… 3B
<missing> N/A Layer: 'spring-cloud-bindings', Created by b… 77.4kB
<missing> N/A Layer: 'helper', Created by buildpack: paket… 3.6MB
<missing> N/A Layer: 'classpath', Created by buildpack: pa… 11B
<missing> N/A Layer: 'jre', Created by buildpack: paketo-b… 275MB
<missing> N/A Layer: 'java-security-properties', Created b… 214B
<missing> N/A Layer: 'helper', Created by buildpack: paket… 5.15MB
<missing> N/A Layer: 'helper', Created by buildpack: paket… 4.49MB
<missing> N/A 519B
<missing> N/A 191B
<missing> N/A COPY /tiny/ / # buildkit 22MB buildkit.dockerfile.v0
As you can see the JRE part of the image is 275 MB and the application part is spread over 5 layers adding up to 53.1 MB.
Alternatively we can use our own Dockerfile and jlink to build a container image:
FROM maven:3.9-eclipse-temurin-25-alpine AS build
COPY app.jar app.jar
RUN jdeps -q \
--recursive \
--print-module-deps \
--ignore-missing-deps \
--multi-release 25 \
app.jar > jdeps.info
RUN jlink --add-modules $(cat jdeps.info) \
--strip-debug \
--no-header-files \
--no-man-pages \
--output /jre
FROM alpine:3.22
ENV JAVA_HOME /jre
ENV PATH $JAVA_HOME/bin:$PATH
COPY --from=build /jre $JAVA_HOME
COPY --from=build app.jar app.jar
ENTRYPOINT java -jar app.jar
First we run jdeps to determine which modules our application needs in the JRE.
Then we run jlink to build a custom JRE that only contains the modules we need.
docker build .
This results in a container image of 153 MB (using JLink).
If we again inspect the layers:
$ docker history app:latest
IMAGE CREATED CREATED BY SIZE COMMENT
2e5ed3ced3f1 3 minutes ago ENTRYPOINT ["/bin/sh" "-c" "java -jar app.ja… 0B buildkit.dockerfile.v0
<missing> 3 minutes ago COPY app.jar app.jar # buildkit 53.1MB buildkit.dockerfile.v0
<missing> 3 minutes ago COPY /jre /jre # buildkit 91.6MB buildkit.dockerfile.v0
<missing> 3 minutes ago ENV PATH=/jre/bin:/usr/local/sbin:/usr/local… 0B buildkit.dockerfile.v0
<missing> 3 minutes ago ENV JAVA_HOME=/jre 0B buildkit.dockerfile.v0
<missing> 5 weeks ago CMD ["/bin/sh"] 0B buildkit.dockerfile.v0
<missing> 5 weeks ago ADD alpine-minirootfs-3.22.2-x86_64.tar.gz /… 8.32MB buildkit.dockerfile.v0
As you can see the JRE part of the image is 91.6 MB and the application is 53.1 MB.
So while JLink does not make our application smaller, it does reduce the size of the JRE inside the image significantly!
Pros of using Spring Boot Maven plugin to build our image:
- Automatic layering of the application for better caching
- Buildpacks that contribute useful things (like certificates, JVM parameters)
Pros of using own Dockerfile and JLink:
- Smaller image size
I think, in larger projects where you have multiple Spring Boot applications deployed that share the same base image,
the layered approach that the Spring Boot Maven plugin provides will result in efficient caching. So while each image is larger,
the overall gains of jlink are diminished as most layers are cached on the machine already.
For smaller projects where you only have one Spring Boot application running on the machine, or your network or storage are limited, the smaller jlink image can be useful.