Java application containers with Gradle and Rockcraft

Introduction

In the previous post of this series, I introduced the Rockcraft Maven plugin. I demonstrated how application container images could be easily created as a part of a Java developer’s typical Maven workflow. This post introduces the Gradle Rockcraft plugin.

You are developing, or maintaining, a Gradle-based Spring Boot application. During your development iterations, you want to test the application in a container that is resembles the deployment container. This post demonstrates how you could do this as a part of your development workflow using Gradle.

💡
If you have read the previous post, you will find repeating content here. This is to make sure that the post is self-sufficient. Feel free to skip ahead.

Setup

I assume you have the Java development environment set up. For this demo, I used a Java 21 on the host, because the generated Ubuntu ROCK has the same version installed.

Ubuntu ROCKs are OCI-compliant container images that can be stored in an OCI-compliant registry like DockerHub and used by any OCI-compliant tool like Docker. Rockcraft is a command-line tool used to create ROCK images from a YAML-based declarative syntax.

Rockcraft is available as a snap and should be installed as a prerequisite.

snap install rockcraft --classic

If not already present, install docker.

snap install docker --classic
💡
If you installed the docker snap it is mandatory to configure it to be run by a normal user. You also need to manually connect the necessary plugs and slots - please refer to the group of snap connect commands in this section of the docker-snap README.

Gradle installation

Spring 3.3 requires Gradle 7.6 and above. Specifically, this demo needs Gradle 8.X. The required version may be installed from the snap store:

# Gradle 8.X is published on the --edge channel only
snap install gradle --classic --edge
💡
Alternatively, you could use the Gradle wrapper (gradlew) that comes with the project.

Building application container images

I use this simple Spring Boot application as an example for this demo.

Step 1: Clone the application repository

git clone https://github.com/spring-guides/gs-spring-boot/
cd gs-spring-boot/initial

Step 2: Configure the Gradle rockcraft plugin

Simply add the Gradle Rockcraft plugin to the list of plugins in the build.gradle file. To keep things simple, we will use the default configuration.

plugins {
    id 'org.springframework.boot' version '3.3.0'
    id 'java'
    id 'io.github.rockcrafters.rockcraft' version '1.1.3' // <<< ADD THIS
}

Step 3: Run the Gradle build

Like the Maven plugin, the Gradle plugin also defines the create-rock, build-rock and push-rock tasks:
1. create-rock creates the Rockcraft YAML
2. build-rock depends on create-rock and builds the rock from the YAML
3. push-rock depends on build-rock and pushes the rock as a docker image to the local docker daemon.

To execute all three tasks, it suffices to invoke the push-rock task:

gradle push-rock -i
💡
The build-rock task might take a couple of minutes to complete, when run for the first time.
💡
The generated Ubuntu ROCK is chiseled container image. You may read about chiseled Java images here.

Step 4: Run the application container

Assuming that the previous step completed successfully, you should now find a new image in your local docker daemon.

$docker image ls
REPOSITORY                        TAG              IMAGE ID       CREATED          SIZE
spring-boot                       0.0.1-SNAPSHOT   c1dd98eb04e5   35 seconds ago   134MB
spring-boot                       latest           c1dd98eb04e5   35 seconds ago   134MB

By default, Ubuntu ROCK images package the Pebble service manager which also acts as the default entrypoint into the image. From a usability point-of-view this only means that the docker run command to launch the container might appear a little different, if you are a regular docker user. We use Pebble’s exec service to launch the Java application:

docker run --network=host spring-boot:latest exec \
    /usr/bin/java -jar /jars/spring-boot-0.0.1-SNAPSHOT.jar

Step 5: Iterate

During local development, you could repeat steps 3 and 4 over and over again. Every time a new image would be created and pushed to the local docker daemon.

Here is a screen-cast that captures steps 1 through 5.

What’s next?

Have you noticed the sizes of the container images? Can we reduce them by packaging only what the application needs? I plan to address this requirement in the next post.

Thank you for reading!

0
Subscribe to my newsletter

Read articles from Pushkar Kulkarni directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Pushkar Kulkarni
Pushkar Kulkarni