Issue
Inspired by question Why is the Java 11 base Docker image so large? (openjdk:11-jre-slim) I found that this topic in Java world is still not settled.
As for 07 Dec 2018
there are common issues/pitfalls (discussed in the ticket above):
JRE is not distributed as a separate "package". Modules from JDK should be used instead
Oracle OpenJDK 11 doesn't support Linux Alpine, so lightweight images can't be easily created
- In the same time current stable Debian versions still doesn't have Java 11 packages (Ubuntu has Java 10 installed under openjdk-11 packages), that's why unstable sid versions are used for base docker images
currently available Oracle openjdk-11 images build unstripped
libjvm.so
module, which has hundreds megabyte and must be stripped separately:
As a result of these issues even slim Oracle Java 11 base images are quite heavy and considered to be unstable: https://hub.docker.com/_/openjdk/
So the question is:
what are optimized or recommended ways to build and deliver Java 11 applications as docker images?
Solution
UPD from 07.2019: https://stackoverflow.com/a/57145029/907576
Taking as an example of simple spring boot application (with only one REST endpoint) so far i was able to figure out the following solutions (considering application jar is located at build/libs/spring-boot-demo.jar
before Docker build:
Jedi path if we want to use official Oracle OpenJDK distribution on stable slim Linux version (
Debian 9 "Stretch"
for now):- use
debian:stretch-slim
(latest stable) base image -
First Docker build stage:
- download and install
Oracle OpenJDK
archive on the first Docker build stage - compile Java minimal distribution for your project (aka JRE) using
jlink
tool
- download and install
Second Docker build stage:
- copy compiled minimal Java distribution from stage 1 to the new image
- configure path to access Java
- copy application jar to the image
So, final
Dockerfile
looks smth like this(actualize JDK
VERSION
,URL
andHASH
value):# First stage: JDK 11 with modules required for Spring Boot FROM debian:stretch-slim as packager # source JDK distribution names # update from https://jdk.java.net/java-se-ri/11 ENV JDK_VERSION="11.0.1" ENV JDK_URL="https://download.java.net/java/GA/jdk11/13/GPL/openjdk-${JDK_VERSION}_linux-x64_bin.tar.gz" ENV JDK_HASH="7a6bb980b9c91c478421f865087ad2d69086a0583aeeb9e69204785e8e97dcfd" ENV JDK_HASH_FILE="${JDK_ARJ_FILE}.sha2" ENV JDK_ARJ_FILE="openjdk-${JDK_VERSION}.tar.gz" # target JDK installation names ENV OPT="/opt" ENV JKD_DIR_NAME="jdk-${JDK_VERSION}" ENV JAVA_HOME="${OPT}/${JKD_DIR_NAME}" ENV JAVA_MINIMAL="${OPT}/java-minimal" # downlodad JDK to the local file ADD "$JDK_URL" "$JDK_ARJ_FILE" # verify downloaded file hashsum RUN { \ echo "Verify downloaded JDK file $JDK_ARJ_FILE:" && \ echo "$JDK_HASH $JDK_ARJ_FILE" > "$JDK_HASH_FILE" && \ sha256sum -c "$JDK_HASH_FILE" ; \ } # extract JDK and add to PATH RUN { \ echo "Unpack downloaded JDK to ${JAVA_HOME}/:" && \ mkdir -p "$OPT" && \ tar xf "$JDK_ARJ_FILE" -C "$OPT" ; \ } ENV PATH="$PATH:$JAVA_HOME/bin" RUN { \ java --version ; \ echo "jlink version:" && \ jlink --version ; \ } # build modules distribution RUN jlink \ --verbose \ --add-modules \ java.base,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \ # java.naming - javax/naming/NamingException # java.desktop - java/beans/PropertyEditorSupport # java.management - javax/management/MBeanServer # java.security.jgss - org/ietf/jgss/GSSException # java.instrument - java/lang/instrument/IllegalClassFormatException --compress 2 \ --strip-debug \ --no-header-files \ --no-man-pages \ --output "$JAVA_MINIMAL" # Second stage, add only our minimal "JRE" distr and our app FROM debian:stretch-slim ENV JAVA_HOME=/opt/java-minimal ENV PATH="$PATH:$JAVA_HOME/bin" COPY --from=packager "$JAVA_HOME" "$JAVA_HOME" COPY "build/libs/spring-boot-demo.jar" "/app.jar" EXPOSE 8080 CMD [ "-jar", "/app.jar" ] ENTRYPOINT [ "java" ]
Note:
- there are 5 java modules included to the minimal JRE example (
java.base,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument
). I found them "manually" running the application and fixingClassNotFoundException
. Waiting for some further Spring Boot developers recommendations/guides which Java modules to include and when, as same as removing some redundant dependencies, likejava.desktop
, which seems to be used only forPropertyEditorSupport
if you are afraid to miss some modules - they are quite lightweight and all of them together give about 2 MB size increasing. Get a full list of
java.*
andjdk.*
11 modules:java --list-modules | grep -E "^java\.[^@]*" | cut -d @ -f 1
java --list-modules | grep -E "^jdk\.[^@]*" | cut -d @ -f 1
The resulting image size in my case was 123 MB with minimal 7 Spring Boot modules and 125 MB with all
java.*
modulesAs an optional improvement of this build workflow:
- Pre-build an image with downloaded and extracted JDK and use it as a base image for first stage
- if you know which modules to include every time - pre-build a base image with compiled minimal JRE and included modules
- use
Easy way with vendor's Open JDK distributions:
Opposite to Oracle Azul's Zulu JDK 11 supports Alpine port and has respective base Docker image.
Thus, if Zulu JVM/JDK is respected, Docker build is much simpler:
FROM azul/zulu-openjdk-alpine:11 as packager
RUN { \
java --version ; \
echo "jlink version:" && \
jlink --version ; \
}
ENV JAVA_MINIMAL=/opt/jre
# build modules distribution
RUN jlink \
--verbose \
--add-modules \
java.base,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \
# java.naming - javax/naming/NamingException
# java.desktop - java/beans/PropertyEditorSupport
# java.management - javax/management/MBeanServer
# java.security.jgss - org/ietf/jgss/GSSException
# java.instrument - java/lang/instrument/IllegalClassFormatException
--compress 2 \
--strip-debug \
--no-header-files \
--no-man-pages \
--output "$JAVA_MINIMAL"
# Second stage, add only our minimal "JRE" distr and our app
FROM alpine
ENV JAVA_MINIMAL=/opt/jre
ENV PATH="$PATH:$JAVA_MINIMAL/bin"
COPY --from=packager "$JAVA_MINIMAL" "$JAVA_MINIMAL"
COPY "build/libs/spring-boot-demo.jar" "/app.jar"
EXPOSE 8080
CMD [ "-jar", "/app.jar" ]
ENTRYPOINT [ "java" ]
The resulting image is 73 MB, as expected with stripped Alpine distributions.
Answered By - radistao
Answer Checked By - Marie Seifert (JavaFixing Admin)