--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 |
--The sample code explained this time is stored below. The following explanation will be given based on this sample code.
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.
#!/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
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)
--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
--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"]
--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
-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