![](/style/images/good.png)
![](/style/images/bad.png)
Dockerizing a Spring Boot application with Gradle
source link: http://bmuschko.com/blog/dockerized-spring-boot-app/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Dockerizing a Spring Boot application
build docker container spring boot gradle ci travis February 8, 2018
The use of Docker has become widespread among companies big and small for a variety scenarios. Executing Docker from the command line for simple tasks is easy and becomes routine as soon as you get a hang of it. Having to enter Docker commands for a whole workflow can become tedious. It seems obvious that you might want to integrate Docker into an automated process for convenience and reproducibility. Gradle can help with defining and executing such a process with the help of the Docker plugin.
Starting with version 3.5.0, the Docker plugin comes with built-in support for dockerizing Spring Boot applications. The configuration described in this blog post has been completely abstracted by the plugin. Enjoy!
This blog post is the first installment of a series on using Docker from Gradle. As a starting point for our journey, we’ll want to package a Spring Boot application as a Docker image and push it to the cloud-based registry service Docker Hub. You will also learn how to automate the process as a Continuous Integration job on Travis CI. You can find the full source code used in this post on GitHub.
Configuring the Docker plugin
The Docker plugin for Gradle provides two main sets of functionality out-of-the-box. It comes with a base plugin for modeling and executing typical Docker commands e.g. for creating an image or starting a container. The base plugin gives you full control over the process you’d like to define.
Additionally, it ships with a convenience abstraction for packaging Java applications as a Docker image. Unfortunately, the Java application abstraction won’t quite work for building Spring Boot applications. In the future, I might implement an abstraction suited for this specific use case. For now, we’ll just use the most basic functionality giving us the most freedom in defining the process we need.
Getting started with the Docker plugin is straightforward. You just have to apply the plugin with the identifier com.bmuschko.docker-remote-api
and the concrete version. Furthermore, you will also have to provide the credentials for the registry we’ll want to use for hosting our image. As you can see in listing 1, the credentials have not been hard-coded in the build script. They can either be provided as environment variables or as project properties (e.g. in a gradle.properties
file in your user home directory). In this workflow, we want to push an image to Docker Hub. The Docker plugin uses the Docker Hub registry by default so no additional configuration is required.
build.gradle
plugins {
id 'com.bmuschko.docker-remote-api' version '3.2.3'
}
docker {
registryCredentials {
username = getConfigurationProperty('DOCKER_USERNAME', 'docker.username')
password = getConfigurationProperty('DOCKER_PASSWORD', 'docker.password')
email = getConfigurationProperty('DOCKER_EMAIL', 'docker.email')
}
}
String getConfigurationProperty(String envVar, String sysProp) {
System.getenv(envVar) ?: project.findProperty(sysProp)
}
Listing 1. Configuring the Docker plugin
With this build script, you just layed the ground work for defining the Docker process. As part of the Docker process, we’ll want to achieve the following tasks:
-
Assemble the Spring Boot application WAR file
-
Create a Dockerfile containing the instructions for the Docker image
-
Use the Dockerfile to build an image
-
Try out the image locally by starting a container to verify that it works as expected
-
Push the image to Docker Hub
Let’s get started by creating the Dockerfile.
Creating the Dockerfile
The Docker plugins provides custom task types for implementing the most common operations in the Docker world. One of the operations is the creation of a Dockerfile. The task type Dockerfile
exposes methods for populating a Dockerfile with the necessary instructions for an image.
Listing 2 demonstrates the use of the task type in a build script. An important aspect of the task definition is that we’ll want to copy the WAR file when creating the image. When starting the image inside of a container, the application’s main class is automatically executed with the java
command. As soon as the application is up and running, the Docker container will expose its functionality through port 8080. You can also see that the Dockerfile performs a health check on the service to indicate its readiness to serve requests. The health check can be achived by calling a curl
command. Notice that there are more lightweight approaches available.
build.gradle
import com.bmuschko.gradle.docker.tasks.image.Dockerfile
task createDockerfile(type: Dockerfile) {
destFile = project.file('build/docker/Dockerfile')
from 'openjdk:8-jre-alpine'
maintainer 'Benjamin Muschko "[email protected]"'
copyFile war.archiveName, '/app/account-web-service.war'
entryPoint 'java'
defaultCommand '-jar', '/app/account-web-service.war'
exposePort 8080
runCommand 'apk --update --no-cache add curl'
instruction 'HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1'
}
task syncWebAppArchive(type: Sync) {
dependsOn assemble
from war.archivePath
into createDockerfile.destFile.parentFile
}
createDockerfile.dependsOn syncWebAppArchive
Listing 2. Creating the Dockerfile
Executing the task createDockerfile
produces the following Dockerfile contents in the directory build/docker
:
build/docker/Dockerfile
FROM openjdk:8-jre-alpine MAINTAINER Benjamin Muschko "[email protected]" COPY account-web-service-1.0.0.war /app/account-web-service.war ENTRYPOINT ["java"] CMD ["-jar", "/app/account-web-service.war"] EXPOSE 8080 RUN apk --update --no-cache add curl HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1
Listing 3. The generated Dockerfile
The task automatically translates its declarative syntax into the underlying Docker instructions. For the most part (except for the HEALTHCHECK
) you don’t even have to know how they need to look like in Docker lingo.
Building the Docker image
Now that you have a Dockerfile in place, you can use the definition to build an image. The task type DockerBuildImage
takes care of the underlying implementation details. You just need to point it to the directory holding the necessary files (Dockerfile and War file) and provide a tag. The tag is a combination of the target repository and the version of the project. You will need to have Docker running to be able to produce the image.
build.gradle
import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage
task buildImage(type: DockerBuildImage) {
dependsOn createDockerfile
inputDir = createDockerfile.destFile.parentFile
tag = "bmuschko/account-web-service:$war.version"
}
Listing 4. Building the Docker image
Running the image in a container
Before pushing the image to the public repository, you should make sure that it is working as expected. First, we’ll want to identify that the image exists and then start up the container to see if the Spring Boot application behaves as expected. For now, we’ll just run the plain Docker commands.
The images
command lists the available images. Alternatively, you can also create a task of type DockerListImages
in your build script.
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE bmuschko/account-web-service 1.0.0 91db93d1be41 5 days ago 98.5MB
Great, the image is available for consumption. Next, you will start a container for the image. You start a container with the run
command. The command renders the container ID in the console for future reference.
$ docker run -d -p 8080:8080 bmuschko/account-web-service:1.0.0 670757d71ccc94b044946497c721dac956a837392c87497027af06244e5fd853
The Docker plugin also supports task types for creating, starting and stopping containers. However, it makes more sense to explain the task types with the help of a more specific use case (covered in the next blog post).
You can also discover all running containers by listing them with the container ls
command.
$ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 670757d71ccc bmuschko/account-web-service:1.0.0 "java -jar /app/acco…" 32 minutes ago Up 32 minutes (healthy) 0.0.0.0:8080->8080/tcp angry_einstein
The application is ready for use as soon as the status turns "healthy". In practice that means that the curl
command could successfully resolve the URL in the Dockerfile. We can verify one of the application’s endpoints by calling the URL http://localhost:8080/accounts?id=1
in a browser. The HTTP response returns a JSON structure representing a bank account.
{ "id":1, "owner":"John Doe", "balance":34024.2300000000032014213502407073974609375 }
We know that the application works properly within a Docker container. Next, you will push the image for consumption by other users.
Pushing the Docker image to a registry
A Docker registry allows you to store and share images. Docker Hub is a free, cloud-based solution for anyone that willing to make images publicly-available. Think Github for Docker images. Of course you can also configure the Docker plugin to use a private registry. See the plugin documentation for more information.
Below you can find the task for pushing the image we created earlier. Providing the tag of the image is the key aspect of configuring the task.
build.gradle
import com.bmuschko.gradle.docker.tasks.image.DockerPushImage
task pushImage(type: DockerPushImage) {
dependsOn buildImage
conventionMapping.imageName = { buildImage.getTag() }
}
Listing 5. Pushing the Docker image to Dockerhub
This task represents the main entry point for our worflow. You might have noticed that we established task dependencies between the tasks shown in the previous sections. Executing pushImage
will create the Dockerfile, produce the image with the latest changes in the WAR file and push the image to Docker Hub.
Creating and pushing the image on Travis CI
You may want to integrate this workflow on a CI server to enable Continuous Deployment for your project. In this section, you will learn how to achieve this with Travis CI. Travis CI can build Docker images and push them to a registry. To use Docker, you will need to enable the service.
With the Travis CI configuration shown in listing 6, you will automatically build a new image of the application and push it to Docker Hub - with every single commit! Remember that you’ll need to create the environment variables for the Docker Hub credentials and your email.
.travis.yml
language: java
install: true
sudo: required
services:
- docker
jdk:
- oraclejdk8
script:
- ./gradlew pushImage -s
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
Listing 6. Creating and pushing the image on Travis CI
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK