1

I use Docker with a Java application, previously I used Java 8 JRE and my total docker image size was 163MB, I then moved to use Java 11 JRE and size increased to 230MB, I would prefer not to increase the size if possible.

But Java 11 allows you to build your own JRE (using jlink from the JDK) containing only the modules you need. So I modified my DockerFile to be based on a JDK rather than JRE, I then used this to build the JRE with only the modules I needed and created this within my application folder. I then used rm -fr /opt/java to remove the JDK as I no longer need it, assuming this would shrink the image size down, but it doesn't the image is now 553MB. My Applications runs but there is no point using jlink if I cannot shrink the image down in size, what am I doing wrong ?

Docker File below:

FROM adoptopenjdk/openjdk11:alpine

RUN apk --no-cache add \
      ca-certificates \
      curl \
      fontconfig \
      msttcorefonts-installer \
      tini \
 && update-ms-fonts \
 && fc-cache -f

RUN mkdir -p /opt \
 && curl http://www.jthink.net/songkong/downloads/build1114/songkong-linux-docker.tgz?val=130| tar -C /opt -xzf - \
&& find /opt/songkong -perm /u+x -type f -print0 | xargs -0 chmod a+x

RUN /opt/java/openjdk/bin/jlink --module-path=/opt/java/openjdk/jmods \
--add-modules java.desktop,java.datatransfer,java.logging,java.management,java.naming,java.net.http,java.prefs,java.scripting,java.sql,jdk.management,jdk.unsupported,jdk.scripting.nashorn \
--output /opt/songkong/jre

RUN rm -fr /opt/java

EXPOSE 4567

ENTRYPOINT ["/sbin/tini"]

# Config, License, Logs, Reports and Internal Database
VOLUME /songkong

# Music folder should be mounted here
VOLUME /music

WORKDIR /opt/songkong

CMD /opt/songkong/songkongremote.sh
Paul Taylor
  • 13,411
  • 42
  • 184
  • 351
  • one recommendation would be to combine all RUN commands into a bash script. So all those intermediate layers can be brought down to just one layer. More the RUN commands, higher the size generally. – akazuko Apr 23 '20 at 17:49
  • 1
    as mentioned in docker official documentation here: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#minimize-the-number-of-layers – akazuko Apr 23 '20 at 17:50

3 Answers3

4

It's normal by the nature of a docker image. A Docker image is base on multiple layer that stack together and each layer are immutable (could not update an another layer content).

So when a file is created in one layer and deleted in an another layer, the file still exist, but no more available and most instruction in a Dockerfile create a new layer.

In your case, we will reduce your image to 3 layer for the demonstration:

[Layer that remove the /opt/java folder]
[Layer with the new JRE]
[Base image with the JDK]

But, in fine, your image have the 3 layers with all the data. You can visualize it by running docker image history myimage, you will have a list of layers and their size.

If you want to reduce the size, you will need to do a multiple stage build: - The first stage create the JRE - The second stage import the JRE and add your code on it

Each stage have different base image, so the second one could use a small base image:

# First stage - Create the JRE
FROM adoptopenjdk/openjdk11:alpine AS jre

RUN /opt/java/openjdk/bin/jlink --module-path=/opt/java/openjdk/jmods \
--add-modules java.desktop,java.datatransfer,java.logging,java.management,java.naming,java.net.http,java.prefs,java.scripting,java.sql,jdk.management,jdk.unsupported,jdk.scripting.nashorn \
--output /opt/songkong/jre

# Second stage
FROM alpine
RUN apk --no-cache add \
      ca-certificates \
      curl \
      fontconfig \
      msttcorefonts-installer \
      tini \
 && update-ms-fonts \
 && fc-cache -f

COPY --from=jre /opt/songkong/jre /opt/songkong/jre

RUN mkdir -p /opt \
 && curl http://www.jthink.net/songkong/downloads/build1114/songkong-linux-docker.tgz?val=130| tar -C /opt -xzf - \
&& find /opt/songkong -perm /u+x -type f -print0 | xargs -0 chmod a+x

EXPOSE 4567

ENTRYPOINT ["/sbin/tini"]

# Config, License, Logs, Reports and Internal Database
VOLUME /songkong

# Music folder should be mounted here
VOLUME /music

WORKDIR /opt/songkong

CMD /opt/songkong/songkongremote.sh

For more information about multi-stage build: https://docs.docker.com/develop/develop-images/multistage-build/

For more information about image and layers: https://docs.docker.com/storage/storagedriver/#images-and-layers

jmaitrehenry
  • 2,190
  • 21
  • 31
  • Hi, thanks I get the concept now but there a small problem with above. Although the above builds a jre and puts into a smaller image java will not actually run, complains it cannot find it but it is not because I put the wrong path but because java relies on libraries not available in base alpine image, i had to copy from jre image but now looks messy. I paste my version on my answer if yo could help. – Paul Taylor Apr 24 '20 at 07:09
  • 1
    @PaulTaylor Your solution is correct, you check how the base alpine image was made on you try to do the same. But it's hard to say what is needed or not by your JRE. – jmaitrehenry Apr 29 '20 at 19:20
0

Ive done some reading and now understand that each layer is built on top of other layer. Since /opt/java is create as part of the base layer it will always exist in the image, even if I delete it in a later layer.

What I need to do is multi-stage builds instead whereby I build my jre in one stage, and then in a second stage based on base image without the jdk I can just copy the jre I created in the first stage into the second stage

I found article explained things quite well.

https://medium.com/@greut/java11-jlink-and-docker-2fec885fb2d

But I had one issue the base alpine:3.11 image cannot run Java out of the box, I nabbed a line from adoptopenjdk/openjdk11:alpine-jre to install various additional packages but I dont know which ones are needed to just run java with provided jre so I have probably installed to much.

FROM adoptopenjdk/openjdk11:alpine AS build

RUN /opt/java/openjdk/bin/jlink --module-path=/opt/java/openjdk/jmods \
--add-modules java.desktop,java.datatransfer,java.logging,java.management,java.naming,java.net.http,java.prefs,java.scripting,java.sql,jdk.management,jdk.unsupported,jdk.scripting.nashorn \
--output /opt/songkong/jre

RUN apk --no-cache add \
      curl \
      tini

RUN mkdir -p /opt \
 && curl http://www.jthink.net/songkong/downloads/build1114/songkong-linux-docker.tgz?val=131| tar -C /opt -xzf - \
&& find /opt/songkong -perm /u+x -type f -print0 | xargs -0 chmod a+x

RUN rm -fr /opt/java

FROM alpine:3.11

ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8'

RUN apk add --no-cache --virtual .build-deps curl binutils \
    && GLIBC_VER="2.31-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-9.1.0-2-x86_64.pkg.tar.xz" \
    && GCC_LIBS_SHA256="91dba90f3c20d32fcf7f1dbe91523653018aa0b8d2230b00f822f6722804cf08" \
    && ZLIB_URL="https://archive.archlinux.org/packages/z/zlib/zlib-1%3A1.2.11-3-x86_64.pkg.tar.xz" \
    && ZLIB_SHA256=17aede0b9f8baa789c5aa3f358fbf8c68a5f1228c5e6cba1a5dd34102ef4d4e5 \
    && curl -LfsS https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub -o /etc/apk/keys/sgerrand.rsa.pub \
    && SGERRAND_RSA_SHA256="823b54589c93b02497f1ba4dc622eaef9c813e6b0f0ebbb2f771e32adf9f4ef2" \
    && echo "${SGERRAND_RSA_SHA256} */etc/apk/keys/sgerrand.rsa.pub" | sha256sum -c - \
    && curl -LfsS ${ALPINE_GLIBC_REPO}/${GLIBC_VER}/glibc-${GLIBC_VER}.apk > /tmp/glibc-${GLIBC_VER}.apk \
    && apk add --no-cache /tmp/glibc-${GLIBC_VER}.apk \
    && curl -LfsS ${ALPINE_GLIBC_REPO}/${GLIBC_VER}/glibc-bin-${GLIBC_VER}.apk > /tmp/glibc-bin-${GLIBC_VER}.apk \
    && apk add --no-cache /tmp/glibc-bin-${GLIBC_VER}.apk \
    && curl -Ls ${ALPINE_GLIBC_REPO}/${GLIBC_VER}/glibc-i18n-${GLIBC_VER}.apk > /tmp/glibc-i18n-${GLIBC_VER}.apk \
    && apk add --no-cache /tmp/glibc-i18n-${GLIBC_VER}.apk \
    && /usr/glibc-compat/bin/localedef --force --inputfile POSIX --charmap UTF-8 "$LANG" || true \
    && echo "export LANG=$LANG" > /etc/profile.d/locale.sh \
    && curl -LfsS ${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 -LfsS ${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 --purge .build-deps glibc-i18n \
    && rm -rf /tmp/*.apk /tmp/gcc /tmp/gcc-libs.tar.xz /tmp/libz /tmp/libz.tar.xz /var/cache/apk/*

RUN mkdir -p /opt

COPY --from=build /opt/songkong /opt/songkong

RUN apk --no-cache add \
      ca-certificates \
      curl \
      fontconfig \
      msttcorefonts-installer \
      tini \
 && update-ms-fonts \
 && fc-cache -f

EXPOSE 4567

ENTRYPOINT ["/sbin/tini"]

# Config, License, Logs, Reports and Internal Database
VOLUME /songkong

# Music folder should be mounted here
VOLUME /music

WORKDIR /opt/songkong

CMD /opt/songkong/songkongremote.sh
Paul Taylor
  • 13,411
  • 42
  • 184
  • 351
  • @Rob it is a complete answer in that it works, its not a perfect answer but such an answer has not yet been forthcoming – Paul Taylor Apr 30 '20 at 08:46
  • You should edit this to make it read like an answer. As it stands, it doesn't look that way. – Rob Apr 30 '20 at 12:09
0

You can also squash the image:

docker build --squash -t your/imagename .

This will combine all layers, e.g. flatten the layers.

orby
  • 267
  • 4
  • 6