Java: creating OCI-compatible image for Spring Boot web using buildah

In a previous article, I provided a full step-by-step example of creating and running a Spring Boot web application that was packaged as a Docker image and run using the local Docker daemon.

In this article, I want to focus on the same Spring Boot REST web application, but without using Docker.  Instead we will use buildah to create the OCI-compatible image, and then podman to run that image.

We will use a custom gradle task to invoke buildah to generate the image, and another task to run the container using podman.

Prerequisites

This article assumes you are on an Ubuntu development server. You will need the following packages installed.

OpenJDK 11+

# refresh package repos
sudo apt update

# show available openjdk versions
sudo apt search openjdk-* | grep -P '^openjdk-1\d-jdk/'

# pick latest (which is 17 as of this writing)
sudo apt install openjdk-17-jdk curl git -y

# validate version reported is the one just installed
java --version

buildah and podman utilties

Use the instructions in my article on installing buildah and podman using a custom apt repository.

Code for article

Pull down the project from github.

git clone https://github.com/fabianlee/spring-boot-with-buildah.git
cd spring-boot-with-buildah

# compile and build Jar
./gradlew bootJar

# build OCI image with buildah
./gradlew buildah

# shows locally built images
buildah images

We can see that the image has been built, but before we run it, let’s look deeper into the custom gradle tasks.

Modifications to build.gradle

‘buildah’ task

Here are the custom task definitions in build.gradle that allow us to invoke ‘gradle buildah’ and have an image generated.

task prepareDockerfileTemplate(type: Copy) {
    group "OCI"
    dependsOn "bootJar"
    from "src/main/resources/docker"
    include "Dockerfile"
    filter { it.replaceAll('<%=name%>', project.name) }
    filter { it.replaceAll('<%=version%>', project.version) }
    into "$buildDir"
}

task buildah(type: Exec) {
    group "OCI"
    dependsOn "prepareDockerfileTemplate"
    workingDir "${buildDir}"
    println "Executing 'buildah' task"
    commandLine "buildah", "bud", "-f", "Dockerfile", "-t", "springbootwithbuildah"
}

The ‘prepareDockerfileTemplate’ is just a filter that uses the ‘src/main/resources/docker’ file as a template and places it into the ‘build’ directory with the correct name and version.

The ‘buildah’ task calls the prepareDockerfileTemplate as a prerequisite, then invokes the command below which will create the image.

buildah bud -f Dockerfile -t springbootwithbuildah

‘podman’ task

Here are the custom task definitions in build.gradle that allow us to invoke ‘gradle podman’ and run the image.

task podmanCleanup(type: Exec) {
    ignoreExitValue true
    commandLine "podman", "rm", "springbootwithbuildah"
}
task podman(type: Exec) {
    group "OCI"
    dependsOn podmanCleanup

    commandLine "podman", "run", "-p", "8080:8080", "-p", "8081:8081", "--name", "springbootwithbuildah", "springbootwithbuildah"
}

The ‘podmanCleanup’ command makes sure any older containers using this name are removed.

The ‘podman’ task calls podmanCleanup as a prerequisite and then invokes the command below which runs the image as a container that is bound to the local ports 8080 and 8081.

podman run -p 8080:8080 -p 8081:8081 --name springbootwithbuildah springbootwithbuildah

Validate running container

This image exposes two ports: 8080 running the main REST api services, and another at 8081 which exposes actuator metrics.

Validate User Controller endpoint ‘:8080/api/user’ lists all users

The UserController.java exposes a REST compliant GET method at ‘/api/user’ that returns the list of currently known User objects in json format.

$ curl -s http://localhost:8080/api/user | jq
[
  {
    "name": "moe"
  },
  {
    "name": "larry"
  },
  {
    "name": "curly"
  }
]

Validate custom health check at ‘:8081/actuator/health’

The health check will return status=UP as long as the user count is greater than 0.

$ curl -s http://localhost:8081/actuator/health | jq
{
  "status": "UP",
  "components": {
    "diskSpace": {
      "status": "UP",
      "details": {
        "total": 502467059712,
        "free": 385481076736,
        "threshold": 10485760,
        "exists": true
      }
    },
    "ping": {
      "status": "UP"
    },
    "user": {
      "status": "UP",
      "details": {
        "usercount": 3
      }
    }
  }
}

Cleanup

# this command OR Ctrl-C from console where it is running in foreground
podman stop springbootwithbuildah

# remove exited container
podman rm springbootwithbuildah

 

REFERENCES

gradle docs

github project code, spring-boot-with-buildah

buildah site

podman site

github jshift, buildah plugin for gradle

mavenlibs, jshift gradle plugin for buildah

NOTES

buildah to push local image to DockerHub

# owner/project:version
OPV=fabianlee/springbootwithbuildah:1.0.0
buildah images $OPV

# make sure login to DockerHub established
buildah login --username <username> --password <password> docker.io 

# tag for remote Docker Hub, then push
buildah tag localhost/$OPV docker.io/$OPV
buildah push docker.io/$OPV