Dockerizing Java 10 Spring Boot app

In my previous article I’ve been talking about using Java 10 to build and run your Spring Boot (opens new window) app. Now it is time to talk how to put it in Docker container. Better support of containerization was one of the main features of Java 10 release. So, let’s go! That is fairly simple process, so just take a look on following code snippet:

FROM debian:9-slim AS builder

RUN set -ex && \
    apt-get update && apt-get install -y wget unzip && \
    wget https://download.java.net/java/GA/jdk11/9/GPL/openjdk-11.0.2_linux-x64_bin.tar.gz -nv -O jdk.tar.gz -nv && \
    mkdir -p /opt/jdk && \
    tar zxvf jdk.tar.gz -C /opt/jdk --strip-components=1 && \
    rm jdk.tar.gz && \
    rm /opt/jdk/lib/src.zip

RUN /opt/jdk/bin/jlink \
    --module-path /opt/jdk/jmods \
    --verbose \
    --add-modules java.base,java.logging,java.xml,jdk.unsupported,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \
    --output /opt/jdk-minimal \
    --compress 2 \
    --no-header-files

# Second stage, add only our custom jdk9 distro and our app
FROM debian:9-slim
COPY --from=builder /opt/jdk-minimal /opt/jdk-minimal

ENV JAVA_HOME=/opt/jdk-minimal
ENV PATH="$PATH:$JAVA_HOME/bin"
ENV APP_TIMEZONE=UTC

EXPOSE 8080
ENTRYPOINT ["/launch.sh"]
COPY launch.sh /
RUN chmod +x /launch.sh

COPY build/libs/you-app.jar /app.jar

Here, as you can see, I use multi-stage Docker build file. Quick note: multi-stage builds are supported with Docker 17.05 or higher but result image is backward compatible(at least from my experience). On the first stage it downloads JDK from Oracle’s, but it can make sense to store your own copy somewhere closer to your build server since it isn’t that small — a little bit less then 200mb. So, it is downloaded, unziped and ready to be used. But not so quick.

As you probably remember, Java 9 gave us module system and JDK itself is also modularized. That means we don’t have to bring whole JDK to production but we could build our own striped version specifically for our app’s needs. There is jlink tool provided for that. Just take a look how it is used in the build process. The modules listed there should be enough to run a typical Spring Boot application. In my production case I had to put java.scripting, jdk.scripting.nashorn to the list as well.

On the second stage it copies results from previous one, defines some environment variables, exposes port, copy launch.sh and app’s artifact. Not a rocket science at all.

And here is launch.sh script I’ve just mentioned:

#!/bin/bash

check_var(){
  if [ -z "$1" ]; then
    >&2 echo "error: missing environmental variable $2. exit"
    exit 1
  fi
}

check_var "${JAVA_XMS}" "JAVA_XMS"
check_var "${JAVA_XMX}" "JAVA_XMX"
check_var "${APP_TIMEZONE}" "APP_TIMEZONE"

XMS=${JAVA_XMS:-none}
XMX=${JAVA_XMX:-none}
TIMEZONE=${APP_TIMEZONE:-none}

JAVA_OPTS="-Xmx${XMX} -Xms${XMS} -Duser.timezone=${TIMEZONE} -Dspring.profiles.active=${SPRING_PROFILES_ACTIVE} ${OPTIONS}"

java ${JAVA_OPTS} -jar /app.jar

That is just a small helper to ensure that there are all important things provided, etc. But, sure, it is optional.

And that should be enough to put your app to the production. Please, put some comments down if you have some thoughts how to make it better!