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
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?
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):
debian:stretch-slim
(latest stable) base imageFirst Docker build stage:
Oracle OpenJDK
archive on the first Docker build stagejlink
toolSecond Docker build stage:
So, final Dockerfile
looks smth like this
(actualize JDK VERSION
, URL
and HASH
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:
java.base,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument
). I found them "manually" running the application and fixing ClassNotFoundException
. Waiting for some further Spring Boot developers recommendations/guides which Java modules to include and when, as same as removing some redundant dependencies, like java.desktop
, which seems to be used only for PropertyEditorSupport
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.*
and jdk.*
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.*
modules
As an optional improvement of this build workflow:
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.