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