0

I would like to package my Spring Boot application as a Docker container using Jib with Gradle.

More Other than following Jib's readme, I have copied a sample from an simple JHipster project. Guess what? It didn't work.

Okay, here is the code.

JHipster generator creates a dedicated gradle/docker.gradle file that is applied from the main Gradle build file, and is importing the Jib plugin

So I copied the JH jib file to start

My gradle/jib.gradle

jib {
    from {
//        image = "adoptopenjdk:8-jre"
        image = "adoptopenjdk:11-jre-hotspot"
    }
    to {
        image = "myapp:latest"
    }
    container {
        entrypoint = ["bash", "-c", "/entrypoint.sh"]
        ports = ["8080"]
        environment = [
            SPRING_OUTPUT_ANSI_ENABLED: "ALWAYS",
            SPRBOOT_SLEEP: "0"
        ]
        creationTime = "USE_CURRENT_TIMESTAMP"
    }
    extraDirectories {
      paths = file("src/main/jib")
      permissions = ["/entrypoint.sh": "755"]
    }
}

The commented part is because I want to use JDK8 but I am trying with what JHipster did.

Second, I copied my own entrypoint.sh based on JH's repository. Here is src/main/jib/entrypoint.sh

#!/bin/sh

echo "The application will start in ${SPRBOOT_SLEEP}s..." && sleep "${SPRBOOT_SLEEP}" # Author's note: it's pointless
exec java "${JAVA_OPTS}" -noverify \
  -XX:+AlwaysPreTouch \
  -Djava.security.egd=file:/dev/./urandom \
  -cp /app/resources/:/app/classes/:/app/libs/* \
  "com.acme.MyApplication" "$@"

And of course I had to add the jib plugin 3.0.0 to my Gradle file, applying the jib.gradle script

plugins {
    id 'org.springframework.boot' version '2.4.2'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
    id 'war'
    id "com.google.cloud.tools.jib" version '3.0.0'
    id 'org.unbroken-dome.test-sets' version '3.0.1'
    id 'org.flywaydb.flyway' version '7.5.4' apply false
    id 'com.telenia.gradle.MybatisGenerator' version '2.1.5'
    id 'net.ltgt.apt-eclipse' version '0.21' apply false
}

apply from: 'gradle/jib.gradle'

Explanation time

Now I expect that gradle jibDockerBuild creates a container with my application's files under /app, and starts the Spring Boot executable using the entrypoint. I don't care about the sleep, which is 0 and is inherited by JH

Of course, when I build the Docker image from the JHipster project, it works like a charm. When I build my own container, running it fails with the classic Unable to locate main class error

The application will start in 0s...

Error: Could not find or load main class

Caused by: java.lang.ClassNotFoundException:

I tried to bash into the container and there is no /app directory at all. There is an /app directory under JHipster's built container.

Trying to inspect both images while writing this post, I have found a few differences. JHipster deploys files under /app, while my Gradle script deploys jars under /var/lib/jetty/webapps/ROOT. My application enables the war plugin consistently, while JHipster enables the plugin only if the -Pwar variable is set.

I don't care about using war or embedded server in the image, as soon as the result is production-grade. I still need to be able to build a war file.

Question

How to properly configure an existing Gradle-based Spring Boot project to be built as a Docker container using Jib? What is wrong in my code, given I gave priority to the existing code from JHipster which is Spring Boot based?

Edit 1

Cool: On JHipster project, gradle -Pwar jibDockerBuild deploys files under /jetty/webapps inside the container and when I start the application I get the magic error Could not find or load main class

usr-local-ΕΨΗΕΛΩΝ
  • 26,101
  • 30
  • 154
  • 305

2 Answers2

2

My mistake was not reading Jib documentation fully.

Jib decides what to do on War projects

Jib also containerizes WAR projects. If the Gradle project uses the WAR Plugin, Jib will by default use jetty as a base image to deploy the project WAR. No extra configuration is necessary other than using the WAR Plugin to make Jib build WAR images.

And this is likely the reason why JHipster demands a -Pwar launch property to expose war tasks, so that you can either build the containerized version of Spring Boot jar, or the web-server-ed container of the resulting war file.

Shame on me for not reading the docs

usr-local-ΕΨΗΕΛΩΝ
  • 26,101
  • 30
  • 154
  • 305
1

For those who may just want to start using Jib with Spring Boot but have an issue for any reasons:

Most of the time, Jib should work out of the box with Spring Boot. Here's a minimal setup for demonstration:

  1. Create a Spring Boot app, for example, with Spring Initializr. It doesn't matter whether it's a Maven or Gradle project:
    $ curl https://start.spring.io/starter.tgz \
        -d type=gradle-project \
        -d dependencies=web \
        -d baseDir=demo | tar -xzvf -
    
  2. Open build.gradle and apply the Jib plugin.
     plugins {
         id 'org.springframework.boot' version '2.4.5'
         id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    +    id 'com.google.cloud.tools.jib' version '3.0.0'
         id 'java'
     }
    
  3. Containerize with Jib.
    $ ./gradlew jibDockerBuild
    
  4. Run the image. (The app will be accessible at localhost:8080.)
    $ docker run --rm -p8080:8080 demo:0.0.1-SNAPSHOT
    

That was a minimal setup, but you'll certainly want to customize Jib configuration further.

Lastly, prefer <packaging>jar to war whenever possible.

Chanseok Oh
  • 3,920
  • 4
  • 23
  • 63