Reducing the size of a docker image with a spring boot application

  • Tutorial

Good afternoon.


Recently, I faced the task of launching spring boot 2 applications in a kubernetes cluster using a docker image. This problem is not new, quickly enough I found examples in Google and packaged my application. I was very surprised not to find the alpine image for jdk11 and hoped that slim would be small enough, but when I sent the image to the docker registry, I noticed that its size was almost 422 megabytes. Under the cat is a description of how I reduced the docker image with my spring boot and java 11 to 144 megabytes.



application


As I mentioned earlier, my application is built using spring boot 2 and is a REST API wrapper over a relational database (using @RepositoryRestResource). My dependencies include:


org.springframework.boot:spring-boot-starter-data-rest
org.springframework.boot:spring-boot-starter-data-jpa
org.flywaydb:flyway-core
org.postgresql:postgresql

The jar file collected has a size of 37.6 megabytes.


Dockerfile:


FROM openjdk:11-jdk-slim
WORKDIR /home/demo
ARG REVISION
COPY target/spring-boot-app-${REVISION}.jar app.jar
ENTRYPOINT ["java","-jar","app.jar"]

As a result of the assembly, I get an image of size: 422 mb according to the output of the docker images command. Interestingly, when using the outdated 8-jdk-slim image, the size is reduced to 306 mb.


Attempt 1: Another Basic Image


The first logical step was an attempt to find a more lightweight image, preferably based on alpine. I scanned the most popular Java repositories:



(11 as the current LTS release and 8 as there are still a sufficient number of applications that could not migrate to more modern versions)


A table with images and tags (~ 2700), their sizes at the time of writing is available here


Here is some of them:


openjdk                       8                   488MB
openjdk                       8-slim              269MB
openjdk                       8-alpine            105MB
openjdk                       8-jdk-slim          269MB
openjdk                       8-jdk-alpine        105MB
openjdk                       8-jre               246MB
openjdk                       8-jre-slim          168MB
openjdk                       8-jre-alpine        84.9MB
openjdk                       11                  604MB
openjdk                       11-slim             384MB
openjdk                       11-jdk              604MB
openjdk                       11-jdk-slim         384MB
openjdk                       11-jre              479MB
openjdk                       11-jre-slim         273MB
adoptopenjdk/openjdk8         alpine              221MB
adoptopenjdk/openjdk8         alpine-slim         89.7MB
adoptopenjdk/openjdk8         jre                 200MB
adoptopenjdk/openjdk8         alpine-jre          121MB
adoptopenjdk/openjdk11        alpine              337MB
adoptopenjdk/openjdk11        alpine-slim         246MB
adoptopenjdk/openjdk11        jre                 218MB
adoptopenjdk/openjdk11        alpine-jre          140MB

Thus, if you change the base image to adoptopenjdk / openjdk11: alpine-jre, you can reduce the image with the application to 177 mb.


Attempt 2: custom runtime


Since the release of jdk9 and modularization, it became possible to build your own runtime that contains only those modules that are necessary for your application. You can read more about this functionality here .


Let's try to determine the necessary modules for the test spring boot application:


~/app ᐅ jdeps -s target/app-1.0.0.jar
app-1.0.0.jar -> java.base
app-1.0.0.jar -> java.logging
app-1.0.0.jar -> not found

Ok, it seems that jdeps cannot handle the fat-jar created with spring boot, but we can unzip the archive and write the classpath:


~/app ᐅ jdeps -s -cp target/app-1.0.0/BOOT-INF/lib/*.jar target/app-1.0.0.jar.original
Error: byte-buddy-1.9.12.jar is a multi-release jar file but --multi-release option is not set
~/app ᐅ jdeps -s --multi-release 11 -cp target/app-1.0.0/BOOT-INF/lib/*.jar target/app-1.0.0.jar.original
Error: aspectjweaver-1.9.2.jar is not a multi-release jar file but --multi-release option is set

On this occasion, a bug is currently open: https://bugs.openjdk.java.net/browse/JDK-8207162


I tried downloading jdk12 to get this information, but ran into the following error:


Exception in thread "main" com.sun.tools.classfile.Dependencies$ClassFileError
...
Caused by: com.sun.tools.classfile.ConstantPool$InvalidEntry: unexpected tag at #1: 53

By trial, error and module search by ClassNotFoundException, I determined that my application needs the following modules:


  • java.base
  • java.logging
  • java.sql
  • java.naming
  • java.management
  • java.instrument
  • java.desktop
  • java.security.jgss

Rantime for them can be collected using:


jlink --no-header-files --no-man-pages --compress=2 --strip-debug --add-modules java.base,java.logging,java.sql,java.naming,java.management,java.instrument,java.desktop,java.security.jgss --output /usr/lib/jvm/spring-boot-runtime

Let's try to build a basic docker image using this modules:


FROM openjdk:11-jdk-slim
RUN jlink --no-header-files --no-man-pages --compress=2 --strip-debug --add-modules java.base,java.logging,java.sql,java.naming,java.management,java.instrument,java.desktop,java.security.jgss --output /usr/lib/jvm/spring-boot-runtime
FROM debian:stretch-slim
COPY --from=0 /usr/lib/jvm/spring-boot-runtime /usr/lib/jvm/spring-boot-runtime
RUN ln -s /usr/lib/jvm/spring-boot-runtime/bin/java /usr/bin/java

and collect it:


docker build . -t spring-boot-runtime:openjdk-11-slim

As a result, the size was 106 megabytes, which is significantly smaller than most found base images with openjdk. If you use it for my application, then the resulting size will be 144 megabytes.


Further we can use spring-boot-runtime:openjdk-11-slimas a base image for all spring boot applications if they have similar dependencies. In the case of various dependencies, it is possible to use a multistage image assembly for each of the applications where java runtime will be collected in the first stage, and the archive with the application will be added in the second.


FROM openjdk:11-jdk-slim
RUN jlink --no-header-files --no-man-pages --compress=2 --strip-debug --add-modules java.base,YOUR_MODULES --output /usr/lib/jvm/spring-boot-runtime
FROM debian:stretch-slim
COPY --from=0 /usr/lib/jvm/spring-boot-runtime /usr/lib/jvm/spring-boot-runtime
WORKDIR /home/demo
ARG REVISION
COPY target/app-${REVISION}.jar app.jar
ENTRYPOINT ["/usr/lib/jvm/spring-boot-runtime/bin/java","-jar","app.jar"]

Conclusion


Currently, most docker images for java have a large enough volume, which can negatively affect the start time of the application, especially if the necessary layers are not yet on the server. Using tags with jre or using java modularization, you can build your own runtime, which will significantly reduce the size of the application image.


Also popular now: