[JAVA] How to make Spring Boot Docker Image smaller

wrap up

--Custom JRE and multi-stage builds can be used to reduce Spring Boot Docker Images. --The following is a summary of the results. You can see that it is less than 1/5 the capacity of the official AdoptOpenJDK Docker Image.

AdoptOpenJDK AdoptOpenJDK-Alpine Custom Runtime
436 MB 358 MB 85.5 MB

Sample code

--The sample code explained this time is stored below. The following explanation will be given based on this sample code.

Step ① Get the required module with jdeps

Use the jdeps command to find out which modules the Spring Boot application depends on.

However, when I check the executable jar file of SpringBoot with jdeps, only java.base and java.logging are output, and this JRE alone does not start SpringBoot. Therefore, use the Shell Script introduced at the following site to get the dependent modules.

-I want to narrow down the docker image of Spring Boot to the minimum necessary (September 2019 version)

#!/bin/sh
# jdeps-spring-boot

set -eu

readonly TARGET_JAR=$1
readonly TARGET_VER=$2

#Directory to extract the jar
readonly TMP_DIR="/tmp/app-jar"
mkdir -p ${TMP_DIR}
trap 'rm -rf ${TMP_DIR}' EXIT

#Extract the jar
unzip -q "${TARGET_JAR}" -d "${TMP_DIR}"

#output
jdeps \
    -classpath \'${TMP_DIR}/BOOT-INF/lib/*:${TMP_DIR}/BOOT-INF/classes:${TMP_DIR}\' \
    --print-module-deps \
    --ignore-missing-deps \
    --module-path ${TMP_DIR}/BOOT-INF/lib/javax.activation-api-1.2.0.jar \
    --recursive \
    --multi-release ${TARGET_VER} \
    -quiet \
    ${TMP_DIR}/org ${TMP_DIR}/BOOT-INF/classes ${TMP_DIR}/BOOT-INF/lib/*.jar

When the file name of Shell Script is get-springboot-module.sh, execute as follows.

./get-springboot-module.sh <SpringBoot jar> <Java Version>

Execution example:

./get-springboot-module.sh demo-0.0.1-SNAPSHOT.jar 11

The modules required for the execution result are output.

java.base,java.desktop,java.instrument,java.management.rmi,java.naming,java.prefs,java.scripting,java.security.jgss,java.sql,jdk.httpserver,jdk.unsupported

important point

When you run ./get-springboot-module.sh, run it on Java 12 or higher version.

This is because Java 11 has a bug that causes a NullPointerException when parsing a non-existent class when parsing javafx.media. To avoid this, you need to specify --ignore-missing-deps, which was added from Java 12.

--Reference: [Getting a NPE on Java 11 jdeps tool when scanning Spring Boot .jar files](https://stackoverflow.com/questions/59284163/getting-a-npe-on-java-11-jdeps-tool-when -scanning-spring-boot-jar-files)

Step ② Create a custom JRE

--Prepare the following Dockerfile. --In jlink --add-modules, describe the modules output in step ①.

FROM adoptopenjdk/openjdk11:alpine AS java-build
WORKDIR /jlink
ENV PATH $JAVA_HOME/bin:$PATH
RUN jlink --strip-debug --no-header-files --no-man-pages --compress=2 --module-path $JAVA_HOME \
    --add-modules java.base,java.desktop,java.instrument,java.management.rmi,java.naming,java.prefs,java.scripting,java.security.jgss,java.sql,jdk.httpserver,jdk.unsupported \
    --output jre-min

Step ③ Start SpringBoot on the custom JRE using multi-stage build

--Add the Dockerfile prepared in step ②.

--Added the settings required to start Java on alpine linux by referring to the following site. -Creating a lightweight Java environment that runs on Docker

FROM adoptopenjdk/openjdk11:alpine AS java-build
WORKDIR /jlink
ENV PATH $JAVA_HOME/bin:$PATH
RUN jlink --strip-debug --no-header-files --no-man-pages --compress=2 --module-path $JAVA_HOME \
    --add-modules java.base,java.desktop,java.instrument,java.management.rmi,java.naming,java.prefs,java.scripting,java.security.jgss,java.sql,jdk.httpserver,jdk.unsupported \
    --output jre-min


FROM alpine:3.12.0
USER root

RUN apk --update add --no-cache ca-certificates curl openssl binutils xz \
    && GLIBC_VER="2.28-r0" \
    && ALPINE_GLIBC_REPO="https://github.com/sgerrand/alpine-pkg-glibc/releases/download" \
    && GCC_LIBS_URL="https://archive.archlinux.org/packages/g/gcc-libs/gcc-libs-8.2.1%2B20180831-1-x86_64.pkg.tar.xz" \
    && GCC_LIBS_SHA256=e4b39fb1f5957c5aab5c2ce0c46e03d30426f3b94b9992b009d417ff2d56af4d \
    && ZLIB_URL="https://archive.archlinux.org/packages/z/zlib/zlib-1%3A1.2.9-1-x86_64.pkg.tar.xz" \
    && ZLIB_SHA256=bb0959c08c1735de27abf01440a6f8a17c5c51e61c3b4c707e988c906d3b7f67 \
    && curl -Ls https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub -o /etc/apk/keys/sgerrand.rsa.pub \
    && curl -Ls ${ALPINE_GLIBC_REPO}/${GLIBC_VER}/glibc-${GLIBC_VER}.apk > /tmp/${GLIBC_VER}.apk \
    && apk add /tmp/${GLIBC_VER}.apk \
    && curl -Ls ${GCC_LIBS_URL} -o /tmp/gcc-libs.tar.xz \
    && echo "${GCC_LIBS_SHA256}  /tmp/gcc-libs.tar.xz" | sha256sum -c - \
    && mkdir /tmp/gcc \
    && tar -xf /tmp/gcc-libs.tar.xz -C /tmp/gcc \
    && mv /tmp/gcc/usr/lib/libgcc* /tmp/gcc/usr/lib/libstdc++* /usr/glibc-compat/lib \
    && strip /usr/glibc-compat/lib/libgcc_s.so.* /usr/glibc-compat/lib/libstdc++.so* \
    && curl -Ls ${ZLIB_URL} -o /tmp/libz.tar.xz \
    && echo "${ZLIB_SHA256}  /tmp/libz.tar.xz" | sha256sum -c - \
    && mkdir /tmp/libz \
    && tar -xf /tmp/libz.tar.xz -C /tmp/libz \
    && mv /tmp/libz/usr/lib/libz.so* /usr/glibc-compat/lib \
    && apk del binutils \
    && rm -rf /tmp/${GLIBC_VER}.apk /tmp/gcc /tmp/gcc-libs.tar.xz /tmp/libz /tmp/libz.tar.xz /var/cache/apk/*

COPY --from=java-build /jlink/jre-min /opt/jre-min
COPY ./demo-0.0.1-SNAPSHOT.jar /opt/demo/demo-0.0.1-SNAPSHOT.jar
ENV PATH /opt/jre-min/bin:$PATH

EXPOSE 8080
WORKDIR /
CMD ["java", "-jar", "/opt/demo/demo-0.0.1-SNAPSHOT.jar"]

Run

--Store the SpringBoot jar file in the directory containing the Dockerfile created in step ③, and execute the following command.

docker build -t demo-official-openjdk-custom-runtime:latest .

--Confirm that the image is created

docker images

REPOSITORY                             TAG                 IMAGE ID            CREATED             SIZE
demo-official-openjdk-custom-runtime   latest              49049142374f        About an hour ago   85.5MB

--Confirm that it starts with the following command.

docker run -d -p 8080:8080 --name demo-official-openjdk-custom-runtime demo-official-openjdk-custom-runtime:latest

Other references

-OpenJDK11 dokcer image (1GB) is large, so create a small image (85MB) with alpine linux + jlink

-What is the Docker image of OpenJDK?

Recommended Posts

How to make Spring Boot Docker Image smaller
How to set Spring Boot + PostgreSQL
How to use ModelMapper (Spring boot)
Build Spring Boot + Docker image in Gradle
How to split Spring Boot message file
How to make CsrfRequestDataValueProcessor and original RequestDataValueProcessor coexist on Spring Boot
How to use MyBatis2 (iBatis) with Spring Boot 1.4 (Spring 4)
How to use built-in h2db with spring boot
How to use Spring Boot session attributes (@SessionAttributes)
How to install Docker
How to add a classpath in Spring Boot
How to make shaded-jar
How to give your image to someone with docker
How to bind to property file in Spring Boot
[Spring Boot] How to refer to the property file
Spring Boot --How to set session timeout time
How to make an image partially transparent in Processing
How to set Dependency Injection (DI) for Spring Boot
How to write a unit test for Spring Boot 2
How to deploy to Heroku from a local docker image
How to create a Spring Boot project in IntelliJ
[Spring Boot] How to create a project (for beginners)
How to use CommandLineRunner in Spring Batch of Spring Boot
How to make Laravel faster with Docker for Mac
How to boot by environment with Spring Boot of Maven
Try Spring Boot from 0 to 100.
Java --How to make JTable
Introduction to Spring Boot ① ~ DI ~
How to set Docker nginx
Introduction to Spring Boot ② ~ AOP ~
Spring Boot starting with Docker
Docker × Spring Boot environment construction
Introduction to Spring Boot Part 1
[Rails] How to make seed
Let's write how to make API with SpringBoot + Docker from 0
How to change application.properties settings at boot time in Spring boot
How to call and use API in Java (Spring Boot)
[Personal notes] How to push a Docker image to GitHub Packages
How to control transactions in Spring Boot without using @Transactional
How to use Lombok in Spring
How to use Spring Data JDBC
How to make a Java container
[How to install Spring Data Jpa]
Migrate Docker image to another server
How to make a JDBC driver
How to run JavaFX on Docker
How to make a splash screen
How to make a Jenkins plugin
[Artifactory] How to use Docker repository
Upgrade spring boot from 1.5 series to 2.0 series
How to make a Maven project
How to make a Java array
[Android] How to make Dialog Fragment
How to distinguish ubuntu cloud image
How to build CloudStack using Docker
How to start Camunda with Docker
Spring Boot gradle build with Docker
How to make an image posted using css look like an icon
How to create your own Controller corresponding to / error with Spring Boot
Image Spring Boot app using jib-maven-plugin and start it with Docker
How to crop an image with libGDX