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 a Gradle-based Spring Boot application. During your development iterations, you want to test the application in a container that 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. 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 (container image) 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
💡
You could also use the Gradle wrapper (gradlew) that comes with the project. In fact, using the Gradle wrapper is the recommended approach.

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, I do not define any custom 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

The Gradle plugin defines tasks create-rock, build-rock and push-rock:
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, while running for the first time.
💡
The generated Ubuntu ROCK is chiseled container image. 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 entry-point 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