Creating container images with self-contained Java application images

At the end of the previous post, I promised to talk about container image size reduction by packaging only those Java modules that the application actually depends on. For this we would need a modular Java application that uses the Java Platform Module System. But before we get there, let us see how you could create self-contained Java application images for non-modular applications, which are still the majority.
Background
The jpackage tool was introduced for production-use in Java 16. The primary goal of jpackage is to provide packaging support native to the underlying operating system. For example, jpackage can let you create a .DEB package for your Java application on Debian/Ubuntu or a .RPM package on RHEL.
Why are we discussing jpackage in the context of Java container images? Because jpackage creates a self-contained application runtime image as an intermediate artifact, for non-modular Java applications. It packages the application, all its dependencies and the Java runtime into a launcher executable. For projects that are made up of multiple JAR files with one of them containing an application entry-point, a single application runtime image makes it easier to ship a container image.
jlink, jpackage also lets you selectively add packages to create an optimized runtime image.The Badass Runtime plugin
The Badass Runtime Plugin allows you to create custom runtime images for non-modular Java applications compiled with Gradle. For example, you could add this plugin to the build.gradle of a Spring Boot application and produce a single binary image that bundles the application, the dependent libraries and the Java runtime (which should be JDK 11 or above).
Adding this plugin to the build.gradle file is as simple as:
plugins {
...
...
id 'org.beryx.runtime' version '1.12.5'
}
There are various configuration options that this plugin supports. Configuring the application name and the main class (the entry-point) is mandatory:
application {
mainClass = 'com.my.app.ApplicationMain'
applicationName = 'my-app'
}
There are several optional configuration options detailed here. For example, you may want to use the runtime option to define a set of Java runtime modules that your non-modular application depends on.
runtime {
modules = ['java.base', 'java.desktop', 'java.management']
}
suggestModules task offered by the plugin.Using Badass Runtime along with the Rockcraft Gradle plugin
The Badass Runtime plugin could be used in conjunction with the Rockcraft Gradle plugin to generate container images with a Java application runtime image. I demonstrate this using the Spring Petclinic sample application.
Step 1: Clone the Spring Petclinic project
git clone https://github.com/spring-projects/spring-petclinic && \
cd spring-petclinic
Step 2: Add the Badass runtime plugin to build.gradle
Let’s also configure the main class. This is mandatory.
plugins {
...
...
id 'org.beryx.runtime' version '1.12.5'
}
application {
mainClass = 'org.springframework.samples.petclinic.PetClinicApplication'
applicationName = 'spring-petclinic'
}
settings.gradle file.Step 3: Add the Rockcraft Gradle plugin
plugins {
...
...
'org.beryx.runtime' version '1.12.5'
id 'io.github.rockcrafters.rockcraft' version '1.1.3'
}
Step 4: Build the application using the push-rock task
./gradlew push-rock -i
If this command completes successfully, you should find a container image named spring-petclinic in the local docker daemon.
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
spring-petclinic 3.5.0 cf0d81414f8d About a minute ago 191MB
spring-petclinic latest cf0d81414f8d About a minute ago 191MB
Step 5: Launching the application container
Let’s inspect the contents of the container using pebble. The custom Java application runtime image is located under /image/bin.
$ docker run spring-petclinic:latest exec ls /image/bin
java
jrunscript
keytool
spring-petclinic
spring-petclinic.bat
Next, execute this image.
$ docker run --network=host spring-petclinic:latest exec /image/bin/spring-petclinic
|\ _,,,--,,_
/,`.-'`' ._ \-;;,_
_______ __|,4- ) )_ .;.(__`'-'__ ___ __ _ ___ _______
| | '---''(_/._)-'(_\_) | | | | | | | | |
| _ | ___|_ _| | | | | |_| | | | __ _ _
| |_| | |___ | | | | | | | | | | \ \ \ \
| ___| ___| | | | _| |___| | _ | | _| \ \ \ \
| | | |___ | | | |_| | | | | | | |_ ) ) ) )
|___| |_______| |___| |_______|_______|___|_| |__|___|_______| / / / /
==================================================================/_/_/_/
:: Built with Spring Boot :: 3.5.0
...
...
The Spring Petclinic application must now be accessible at http://localhost:8080.
Image size considerations
Unlike jlink and a modular application, it isn’t very straightforward to strip down unnecessary Java system modules while building an image for a non-modular application. But if you can deduce a minimal list of modules that the application depends on, the size of the image could be trimmed down using the runtime configuration mentioned previously. However, I don’t think it is a common pattern to use jpackage runtime images to achieve this.
The Rockcraft Maven/Gradle plugins adopt the minimal-size strategy by using chiseled base images. A modular Java application and a custom runtime image created using the Badass Jlink plugin may help in further size reductions. This is something we should explore next.
Finally, here is the screen-cast that captures the above steps. Thank you for reading on!




