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 Chuck Norris facts 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 the 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.geowarin
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('http://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" // <- Here
}
}
Apply the Docker plugin:
apply plugin: 'docker'
Finally, add the buildDocker task:
task buildDocker(type: Docker) {
baseImage = 'develar/java:latest'
push = project.hasProperty('push')
tag = 'geowarin/sout-chuck-norris'
addFile {
from jar
rename {'app.jar'}
}
entryPoint(['java', '-Djava.security.egd=file:/dev/./urandom', '-jar', '/app.jar'])
// exposePort(8080)
}
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 geowarin/sout-chuck-norris
(change geowarin to
your user name).
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
geowarin/sout-chuck-norris 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 geowarin/sout-chuck-norris
Unable to find image 'geowarin/sout-chuck-norris:latest' locally
latest: Pulling from geowarin/sout-chuck-norris
09ef480f93cc: Verifying Checksum
a6fb0a3c9260: Download complete
Pulling repository docker.io/geowarin/sout-chuck-norris
914b85281644: Pulling dependent layers
914b85281644: Download complete
Status: Downloaded newer image for geowarin/sout-chuck-norris:latest
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.3.1.RELEASE)
2016-01-12 16:54:19.089 INFO 1 --- [ main] c.g.geowarin.SoutChuckNorrisApplication : Starting SoutChuckNorrisApplication 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.geowarin.MainRunner : Chuck Norris can download emails with his pick-up.
Conclusion
Spring boot producing runnable jars, it is fairly easy to embed them inside of a container.
As usual, do not hesitate to give me your feedback and to checkout the code on github!