Creating a slim modular Java 9 runtime Docker image with Alpine Linux
With the release of Java 9, and the introduction of Project Jigsaw (the Java Platform Module System), we no longer have the need for a full-blown JRE to run our Java applications. It is now possible to construct a stripped-down Java Runtime, containing the minimum set of required modules. This allows us to create slim Docker containers without excess baggage.
Building and running the application
Our example application consists of two Java 9 modules (basically two JARS, with a
We assume the concept of modules is familiar to the reader. If not, you can learn more about it e.g. here: http://openjdk.java.net/projects/jigsaw/
Firstly, we have a
backend module consisting of a class which provides us with a
String (to keep it simple).
The backend module has no explicit dependencies on other modules (only an implicit dependency on
Secondly, we have
frontend module consisting of an executable main class, which gets the String from the backend
and prints it to
System.out (again, very straightforward). This module has an explicit dependency on
and an implicit dependency on
To see the application in action, build it with Maven (
mvn clean package), and run it from the command line:
> java -p frontend-module/target/frontend-module-1.0-SNAPSHOT.jar:backend-module/target/backend-module-1.0-SNAPSHOT.jar \ -m com.jdriven.java9runtime.frontend/com.jdriven.java9runtime.frontend.FrontendApplication
(or use the provided run-app.sh)
-p option sets the module path (similar to the 'old' classpath), and the
-m option specifies the module and class to run.
Creating a custom Java runtime image with JLink
jlink tool, provided with the Java 9 JDK, allows us to combine our application modules with the required modules
from the JDK (in our case, only
java.base) into a custom-tailored JRE. Please note that the generated JRE, like any JRE, is NOT platform independent!
The generated JRE is not portable to other platforms.
Because we would like to run our application inside Docker on top of Alpine Linux (a very small Linux distro, approximately 5 MB),
we need to run
jlink with a JDK that is compatible with the Alpine OS. Unfortunately, at the time of writing,
neither the Oracle JDK nor the OpenJDK release of Java 9 support Alpine Linux yet (see: https://github.com/anapsix/docker-alpine-java/issues/38).
However, there is an OpenJDK Early-Access Build available, which we can use, at: http://jdk.java.net/9/ea
We could install this EA JDK and use it to run
jlink, or we could do it all from inside a Docker container, which is cooler.
The included Dockerfile file is used to perform a multi-stage build. The first stage runs the
jlink command to create the custom JRE.
The second stage creates a tiny runnable Docker image, which executes the JRE created in the first stage.
Build this Docker image using the command:
docker build -t java9-runtime-image . (<-- note the period at the end of the command)
The builder stage of this Docker image (based on Alpine), downloads and installs the EA JDK for Alpine, and runs the
on our sources. To be able to access our compiled jars, we copy them into our image.
jlink command takes the following parameters:
--module-pathonce again sets the module path to include our modules, and the default JDK modules (located at
--add-modulesdefines the set of root modules to include. We only need to include the frontend module. The backend module is included transitively because it is required by frontend.
--launcherspecifies a command name to launch our application, and defines which class in which module is the main class (in the form of
--outputspecifies a destination directory (which should not exist yet!) in which the runtime is generated
The other parameters are included to decrease the image in size, by using compression and stripping some irrelevant data. For more information regarding these parameters, see: https://docs.oracle.com/javase/9/tools/jlink.htm
Building a Docker image for our application
After running JLink, we now have our own custom JRE, which is only approximately 30 MB in size,
in a newly created
/app/dist directory (inside the Docker container).
This of course is still quite large for a hello world app, but compared to the default JRE, which is larger than 200 MB,
it's quite an improvement.
The second stage of the Docker build executes the runtime by using the launcher command defined earlier (
which is located at
Examining this script, generated by JLink, we see that it basically just starts the stripped down JRE,
which contains only our modules and
java.base, supplying the main module and class we provided earlier:
#!/bin/sh JLINK_VM_OPTIONS= DIR=`dirname $0` $DIR/java $JLINK_VM_OPTIONS -m com.jdriven.java9runtime.frontend/com.jdriven.java9runtime.frontend.FrontendApplication $@
Because the created JRE is fully self-contained, all we need is a simple Alpine-based Docker base image to execute
run command. This is exactly what we do in the second stage of our Dockerfile.
The second stage is quite simple. Starting with the Alpine base image, copy the contents of
dist from the first stage into our image,
bin/run as an entrypoint.
To test the image, run an instance by executing:
> docker run --rm java9-runtime-image
(or use the provided docker.sh to build and run it all)
The combination of a small Alpine Linux distro (5 MB) and our stripped down JRE (30 MB),
results in a total Docker image size of approximately 35 MB. By comparison, the
openjdk:8-jre-alpine Docker image is 80 MB.
A reduction of more than 50 percent!
Of course, any real-life software project also includes a number of third-party dependencies, and will almost certainly need more modules from the JRE than this Hello World example. We have yet to see if this approach will give us a significant benefit in real-life applications.