Smaller Java images with Alpine Linux

August 10th 2015 Nicola Paolucci in Java, Docker, Linux

Sometimes I need to be hit in the head with an axe to find a solution to a problem that has been bugging me forever. A minimal Java container has been on my wish list since I found out about Docker and I've been writing about running Java in Docker for sometime already.

alot

Official Java images have historically been mastodontic - cue picture above - I just tried "docker pull java" and I got an image of 816.4MB. A colleague of mine few days ago mentioned Alpine Linux, a minimalistic Linux distribution based on musl libc and BusyBox that comes with a nice package manager. And the base image is ... 5Mb ?! Where have I been hiding? Why didn't I know about this?! Anyway here's my chance to make things right. The objective: to have a minimal Java container for my (and your) applications. Let's dream together.

If OpenJDK 7 is good enough

If you are still using JDK7 and you don't have a strong requirement to have the Oracle version, the easiest and leaner image I found is very simple to setup with this Dockerfile:

FROM alpine:3.2
RUN apk --update add openjdk7-jre
CMD ["/usr/bin/java", "-version"]
  • Build it with:
docker build -t yourname/minimal-java .
  • Or if you are lazy just:
docker pull durdn/minimal-java
  • Now you can test that java is installed with:
docker run -t durdn/minimal-java

Which outputs:

java version "1.7.0_79"
OpenJDK Runtime Environment (IcedTea 2.5.5) (Alpine 7.79.2.5.5-r0)
OpenJDK 64-Bit Server VM (build 24.79-b02, mixed mode)

The result is a Java 7 runtime environment, ready for your Java 7 applications in only 123MB instead than 800+MB. NICE!

What about Oracle JRE/JDK 8?

For many applications teams prefer or require the Oracle JDK. In this case we can't use Alpine package manager (yet), we have to wrangle the installation ourselves from the official Oracle packages. Do you want to see how that's done? This is the list of steps:

  • Install curl, tar, and ca-certificates on the base alpine image.
  • Install glibc-2.21 which is a hard dependency of Java 8.
  • Download the Oracle JRE/JDK using tricks in this SO article.
  • Remove spurious folders not needed (like jdk/jre/lib/desktop and others...).
  • Set the proper environment variables.

The whole process is well laid out amongst others in a clean Dockerfile by anapsix which I list here for completeness:

# AlpineLinux with a glibc-2.21 and Oracle Java 8

FROM alpine:3.2
MAINTAINER Anastas Dancha [...]

# Install cURL
RUN apk --update add curl ca-certificates tar && \
    curl -Ls https://circle-artifacts.com/gh/andyshinn/alpine-pkg-glibc/6/artifacts/0/home/ubuntu/alpine-pkg-glibc/packages/x86_64/glibc-2.21-r2.apk > /tmp/glibc-2.21-r2.apk && \
    apk add --allow-untrusted /tmp/glibc-2.21-r2.apk

# Java Version
ENV JAVA_VERSION_MAJOR 8
ENV JAVA_VERSION_MINOR 45
ENV JAVA_VERSION_BUILD 14
ENV JAVA_PACKAGE       jdk

# Download and unarchive Java
RUN mkdir /opt && curl -jksSLH "Cookie: oraclelicense=accept-securebackup-cookie"\
  http://download.oracle.com/otn-pub/java/jdk/${JAVA_VERSION_MAJOR}u${JAVA_VERSION_MINOR}-b${JAVA_VERSION_BUILD}/${JAVA_PACKAGE}-${JAVA_VERSION_MAJOR}u${JAVA_VERSION_MINOR}-linux-x64.tar.gz \
    | tar -xzf - -C /opt &&\
    ln -s /opt/jdk1.${JAVA_VERSION_MAJOR}.0_${JAVA_VERSION_MINOR} /opt/jdk &&\
    rm -rf /opt/jdk/*src.zip \
           /opt/jdk/lib/missioncontrol \
           /opt/jdk/lib/visualvm \
           /opt/jdk/lib/*javafx* \
           /opt/jdk/jre/lib/plugin.jar \
           /opt/jdk/jre/lib/ext/jfxrt.jar \
           /opt/jdk/jre/bin/javaws \
           /opt/jdk/jre/lib/javaws.jar \
           /opt/jdk/jre/lib/desktop \
           /opt/jdk/jre/plugin \
           /opt/jdk/jre/lib/deploy* \
           /opt/jdk/jre/lib/*javafx* \
           /opt/jdk/jre/lib/*jfx* \
           /opt/jdk/jre/lib/amd64/libdecora_sse.so \
           /opt/jdk/jre/lib/amd64/libprism_*.so \
           /opt/jdk/jre/lib/amd64/libfxplugins.so \
           /opt/jdk/jre/lib/amd64/libglass.so \
           /opt/jdk/jre/lib/amd64/libgstreamer-lite.so \
           /opt/jdk/jre/lib/amd64/libjavafx*.so \
           /opt/jdk/jre/lib/amd64/libjfx*.so

# Set environment
ENV JAVA_HOME /opt/jdk
ENV PATH ${PATH}:${JAVA_HOME}/bin

The result of building this image or pulling from anapsix/alpine-java is a fully functional Oracle Java 8 image weighing only 173Mb. Impressive!

Running Atlassian Stash on it

The whole point of the exercise above was for me to run a leaner container with Stash - our enterprise Git server - trying to shave space off from our official image. The task was a success - if not a smashing one. The final Stash image I produced weighs 368MB which adds up to a ~30% reduction over the official image. Here how I had to tweak the Dockerfile:

FROM durdn/minimal-java8:stripped

MAINTAINER Atlassian Stash Team

ENV DOWNLOAD_URL        https://downloads.atlassian.com/software/stash/downloads/atlassian-stash-

ENV STASH_HOME          /var/atlassian/application-data/stash

RUN apk --update add git tar bash

# Install Atlassian Stash to the following location
ENV STASH_INST_DIR   /opt/atlassian/stash

ENV STASH_VERSION 3.11.1

RUN mkdir -p             ${STASH_INST_DIR}                                                     \
  && curl -LO --silent   ${DOWNLOAD_URL}${STASH_VERSION}.tar.gz                                \
  && tar -xf atlassian-stash-${STASH_VERSION}.tar.gz -C ${STASH_INST_DIR} --strip-components 1 \
  && rm                  atlassian-stash-${STASH_VERSION}.tar.gz                               \
  && mkdir -p            ${STASH_INST_DIR}/conf/Catalina                                       \
  && chmod -R 700        ${STASH_INST_DIR}/conf/Catalina                                       \
  && chmod -R 700        ${STASH_INST_DIR}/logs                                                \
  && chmod -R 700        ${STASH_INST_DIR}/temp                                                \
  && chmod -R 700        ${STASH_INST_DIR}/work                                  

VOLUME ["${STASH_INST_DIR}"]

# HTTP Port
EXPOSE 7990

# SSH Port
EXPOSE 7999

WORKDIR $STASH_INST_DIR

# Run in foreground
CMD ["./bin/start-stash.sh", "-fg"]

Bonus trick: How to strip an image

I love the layering ability of Docker images but for base images upon which I'll build my stacks often I'd like them to consist of a single layer. It's a mental thing more than anything so excuse my weirdness if you can.

Many times the extra layers in your base images will not be re-used. For those situations it can be helpful to strip an image of all its layers and flatten it. The technique to accomplish that is the following:

  • First run it so that you have a container to refer to:
docker run -t durdn/minimal-java /bin/true
  • Then export it and re-import it:
docker export `docker ps -q -n=1` | docker import - durdn/minimal-java:stripped
  • Verify that the new image only has one layer with:
docker history durdn/minimal-java:stripped

IMAGE               CREATED             CREATED BY          SIZE                COMMENT
8eb82b59dee6        31 seconds ago                          172.9 MB            Imported from -

docker run -ti durdn/minimal-java8:stripped /opt/jdk/bin/java -version

java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)

Conclusions

That's it for my discoveries today. Pings, likes, comments, love or hate gladly received here in the comments, at @durdn or at my awesome team @atlassiandev.

(Credit for the epic alot picture goes to Hyperbole and a half).

 


You might also enjoy our ebook, "Hello World! A new grad's guide to coding as a team" – a collection of essays designed to help new programmers succeed in a team setting. Grab it for yourself, your team, or the new computer science graduate in your life. Even seasoned coders might learn a thing or two.

Read it online now

Click here to download for your Kindle