Build a Docker image of your Spring Boot app using Gradle
If you like having a self-contained application as a deliverable for your project, the idea of putting your Spring applications in a Docker container might be appealing to you.
In this article, I will show you a simple way to make a docker image the output of your Gradle build, thanks to the gradle-docker plugin.
The code that we will build is a simple console application powered by Spring Boot that will periodically write some messages to the standard output. It is available on github and on Docker Hub.
Take a Spring boot application
You can easily generate a starter project with start.spring.io or with IntelliJ. We will create a gradle/groovy application with no Spring Boot starter to keep the code very simple.
Click on this link to generate Gradle Project!
Unzip it and open it in your favorite IDE. Since the application is going to loop forever, you can remove the generated test, which would loop forever too.
Add the following dependency to your build.gradle:
compile 'org.codehaus.groovy.modules.http-builder:http-builder:0.7.1'
Since we will use the JSONSlurper, the idiomatic way to parse JSON in groovy, we will need to the change the groovy dependency to groovy-all:
compile 'org.codehaus.groovy:groovy-all'
The code
The code is really simple:
package com.github
import groovy.util.logging.Log4j
import groovyx.net.http.RESTClient
import org.apache.log4j.Level
import org.springframework.boot.CommandLineRunner
import org.springframework.stereotype.Component
@Component
@Log4j
class MainRunner implements CommandLineRunner {
private static Random random = new Random();
@Override
void run(String... args) throws Exception {
while (true) {
log.log(randomLevel(), randomMessage())
sleep 3000
}
}
private Level randomLevel() {
switch (random.nextInt(3)) {
case 0:
return Level.DEBUG;
case 1:
return Level.INFO;
case 2:
return Level.ERROR;
default:
return Level.INFO;
}
}
private String randomMessage() {
def client = new RESTClient('https://api.icndb.com/jokes/')
def response = client.get(path: 'random')
response.data.value.joke
}
}
Build the docker image
Add the plugin repository to find the Docker plugin:
buildscript {
ext {
springBootVersion = '1.3.0.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath "se.transmode.gradle:gradle-docker:1.2"
}
}
Apply the Docker plugin:
apply plugin: 'docker'
Finally, add the buildDocker task:
task buildDocker(type: Docker) {
baseImage = 'myrepo/java:latest'
push = project.hasProperty('push')
tag = 'myrepo/testmessages'
addFile {
from jar
rename {'app.jar'}
}
entryPoint(['java', '-Djava.security.egd=file:/dev/./urandom', '-jar', '/app.jar'])
}
buildDocker.dependsOn(build)
With this Docker plugin, every Docker instruction is available in the Gradle build so you don’t even have to write a Dockerfile.
In this task, we create an image called myrepo/testmessages (change myrepo to your repository username). It will contain only the jar produced by our build, which will be renamed to app.jar. Then, the entry point of the container is simply java -jar app.jar.
The advantage of using an entry point instead of a CMD is that we can append command line arguments to the docker run ... command and those will be passed to our application.
The downside is you cannot use docker exec ... bash to attach to the container.
We use Develar’s java 8 image. It is built on top of Alpine and weights less than 120MB.
You can now run ./gradlew buildDocker to create the docker image containing our project.
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
myrepo/testproject latest 85ff1a728670 4 seconds ago 135.9 MB
Publish the image to the Docker hub
Create an account on the docker hub then use docker login to authenticate your client.
You can now run ./gradlew buildDocker -Ppush to publish your image to docker hub.
Once it is published, anyone can run you application. If the image is not available on their machine, it will be pulled from the docker hub.
$> docker run myrepo/testmessages
Unable to find image 'myrepo/testmessages:latest' locally
latest: Pulling from myrepo/testmessages
09ef480f93cc: Verifying Checksum
a6fb0a3c9260: Download complete
Pulling repository docker.io/myrepo/testmessages
914b85281644: Pulling dependent layers
914b85281644: Download complete
Status: Downloaded newer image for myrepo/testmessages:latest
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.3.1.RELEASE)
2016-01-12 16:54:19.089 INFO 1 --- [ main] c.g.geowarin.SoutChuckNorrisApplication : Starting TestingMessagesApplication on 05d1fedaba4d with PID 1 (/app.jar started by root in /)
2016-01-12 16:54:19.093 INFO 1 --- [ main] c.g.geowarin.SoutChuckNorrisApplication : No active profile set, falling back to default profiles: default
2016-01-12 16:54:19.205 INFO 1 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4533542a: startup date [Tue Jan 12 16:54:19 GMT 2016]; root of context hierarchy
2016-01-12 16:54:20.609 INFO 1 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2016-01-12 16:54:21.456 INFO 1 --- [ main] com.github.MainRunner : You can see messages now
Conclusion
Spring boot producing runnable jars, it is fairly easy to embed them inside of a container.