Reduce docker image size for the java application.

In a previous post Run multiple processes in a docker container we saw how we can utilize the supervisor process manager in order to overcome the fact that the container’s main running process is the ENTRYPOINT and/or CMD at the end of the Dockerfile. For the demo purposes we created a sample application which can be found here.

After building the docker images with the supervisor process manager and once complete, running the docker images command reveals an image size of 1.25GB. The image size seems to be quite large considering the current trend with micro-services, cloud , small footprint, fast releases etc. So given that we need to reduce the docker image size what can we do in order to address it.

As described in the official docker documentation the usage of the supervisor process manager is rather a moderately heavy-weight approach that requires us to package the supervisord and its configuration in our image. So the first step towards reducing the overall image size ( and thus the image download/upload times ) is replacing the supervisord with a simple bash script ( removing supervisor means that we are going to remove also the supervisor configuration files and related resources ).

The new bash script will look like the following


#!/bin/sh

JBOSS_CLI=$JBOSS_HOME/bin/jboss-cli.sh
JBOSS_MODE=${1:-"standalone"}
JBOSS_CONFIG=${2:-"$JBOSS_MODE.xml"}

wait_for_server() {
  until `$JBOSS_CLI -c "ls /deployment" &> /dev/null`; do
    sleep 1
  done
}

echo "=> Starting WildFly server"
$JBOSS_HOME/bin/standalone.sh -b 0.0.0.0 -c standalone-full.xml > /dev/stdout &

echo "=> Waiting for the server to boot"
wait_for_server

echo "=> Executing the commands"
$JBOSS_CLI -c --file=/conf.cli

exec "$@"

On the other hand, as docker image authors we need to consider several things to avoid creating large images. Since docker images use a layered architecture a general rule of thumb is “less layers , smaller size”. To explain : every run instruction in the Dockerfile writes a new layer in the image and every layer requires extra space on disk. In order to keep the number of layers to a minimum any file manipulation should ideally be made under a single RUN instruction.

Another tip is to remove from the container any useless/unnecessary files ( e.g. the apt cache is useless ). Given these 2 things we can modify our Dockerfile as shown bellow

FROM java:8

MAINTAINER Thanassis Sergouniotis "t.sergouniotis@inopus.de"

#remove cache in order to minimize size
RUN rm -rf /var/cache/apk/* \
		&& wget http://download.jboss.org/wildfly/12.0.0.Final/wildfly-12.0.0.Final.tar.gz \
		&& cp wildfly-12.0.0.Final.tar.gz /usr/local \
		&& cd /usr/local && tar xvfz wildfly-12.0.0.Final.tar.gz 

ENV JBOSS_HOME /usr/local/wildfly-12.0.0.Final
ENV LAUNCH_JBOSS_IN_BACKGROUND false

ADD target/libs/postgresql.jar /opt/postgresql.jar
ADD target/webapp.war /opt/webapp.war

ADD docker/run.sh /run.sh
ADD docker/conf.cli /conf.cli

EXPOSE 8080
EXPOSE 8787
EXPOSE 9000
EXPOSE 9999
EXPOSE 9990

ENTRYPOINT ["/bin/sh", "run.sh" ]

CMD tail -f /dev/null

After building the new image we can see that the image size reduced to 1.1GB which is a small improvement. So right now we shift our focus to the base image. The docker images reveals that the base image 726MB. So over the half of our image is the base openjdk image. Fortunately, openjdk has better images now which are based on the a linux alpine base image. The alpine image is fairly lighter than the regular openjdk images which are based on a debian:jessie image. The openjdk:8-jdk-slim image is about 244MB so by modifying the base image to be the openjdk:8-jdk-alpine the image will be 657MB. The new Dockerfile would be


FROM openjdk:8-jdk-alpine

MAINTAINER Thanassis Sergouniotis "t.sergouniotis@inopus.de"

#remove cache in order to minimize size
RUN rm -rf /var/cache/apk/* \
		&& wget http://download.jboss.org/wildfly/12.0.0.Final/wildfly-12.0.0.Final.tar.gz \
		&& cp wildfly-12.0.0.Final.tar.gz /usr/local \
		&& cd /usr/local && tar xvfz wildfly-12.0.0.Final.tar.gz 

ENV JBOSS_HOME /usr/local/wildfly-12.0.0.Final
ENV LAUNCH_JBOSS_IN_BACKGROUND false

ADD target/libs/postgresql.jar /opt/postgresql.jar
ADD target/webapp.war /opt/webapp.war

ADD docker/run.sh /run.sh
ADD docker/conf.cli /conf.cli

EXPOSE 8080
EXPOSE 8787
EXPOSE 9000
EXPOSE 9999
EXPOSE 9990

ENTRYPOINT ["/bin/sh", "run.sh" ]

CMD tail -f /dev/null

You can find the source code for the example used above in the git repository here here
The master branch contains the version with the supervisor process manager while the smaller branch containers the lightweight image version.
Hope you enjoyed the post.