Building Small JREs and Docker Images with Recent Versions of OpenJDK – Alpine edition

24 Nov

I did not plan to write a third part about my journey to small Docker images for OpenJDK. However, Java 16 will support musl-libc instead of glibc which is very interesting for our container world. So I did another experiment which I would like to share.

Java relies only on very few dependencies: zlib and libc. While zlib handles compression, libc is the foundation of a typical C program. It brings support for input and output, math, and many things more. It’s a bit like the provided classes of our Java runtime. A bit! The most common implementation of libc is gnu’s glibc. glibc is very mighty and in addition to io and math, it bring’s a ton of features that we normally don’t need. I.e. glibc brings support for several outdated 7 and 8 bit charsets. Musl-libc is a clean and minimalistic libc implementation from alpine linux. musl provides a comparison of the libc functionalities.

Java 16 comes with a JDK that is built against musl-libc (yes – it is a separate setup that must be downloaded). This is possible because Java actually doesn’t need most of the extra features from glibc. As Java developers, we see no difference. In Docker container we prefer small images. How small is glibc, how small is musl-libc?

Let’s build a musl based JRE base image and compare the sizes!
Like in the previous articles, we build the final image from scratch and copy only the dependencies we need. The following Dockerfile will look familiar:

FROM alpine AS jre

RUN apk add binutils

RUN \
    mkdir -p /tmp/jdk /target/lib; \
    for lib in musl zlib; do \
        apk info -L $lib 2>/dev/null | tail -n +2 | head -n -1 | \
        while read -r file; do \
            cp "$file" "/target/$file" ; \
        done \
    done

COPY jdk.tar.gz /tmp/jdk/jdk.tar.gz

RUN tar -C /tmp/jdk --strip-components=1 \
    -xzf /tmp/jdk/jdk.tar.gz \
    && \
    /tmp/jdk/bin/jlink \
    --add-modules java.se \
    --add-modules jdk.unsupported \
    --output /target/jre \
    --no-header-files \
    --no-man-pages \
    --strip-debug \
    --compress=2


FROM scratch

COPY --from=jre /target /

USER 65534
ENTRYPOINT [ "/jre/bin/java" ]

(For this demo, I downloaded the „Alpine linux/x64“ JDK from https://jdk.java.net/16/. When Java 16 is released, there will be an official download. This also means that the final numbers may change a bit when Java 16 is released!)

The loop in the lines 5-12 only copies for files with a total size of 1.2 MiB. Isn’t this great? The final image is 53.8 MiB large, 52.6 MiB belong to Java. The glibc version of the Docker image is 68.3MiB large. By switching to a smaller libc, we saved more than 15 MiB (~ 32%).

I hope you enjoyed this as much as I did!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert