Bashirk logo
Refume
Technology

Getting Started Building Docker Images with Gradle

Getting Started Building Docker Images with Gradle
0 views
12 min read
#Technology

Introduction

It is no news that Gradle is the most popular build tool for Java Virtual Machine (JVM) projects on GitHub, you can check out the performance benchmarks for Gradle here. In this guide, you will learn about how to build a Docker Image with Gradle. As a gentle reminder, your projects can always be organized into subprojects to structure your software project into useful modules, you would understand what this is like as you progress through the guide. Now, let me get you started with this step-by-step guide.

WHAT YOU'LL BUILD

To reiterate, you'll be building a Docker image with the Gradle Build Tool. In case you're wondering what Docker or a Docker image is, Docker is a set of platform-as-a-service offerings that use OS-level virtualization for software delivery, while a Docker image is a read-only file that includes instructions for creating a Docker container. Want to know more? You should definitely check this article out.

WHAT YOU'LL NEED

Going forward, you'll need some set of resources to successfully build a Docker image for your project, and the following resources that have been itemized below are exactly all what you need to get started;

Resources:

So before getting our hands dirty, it is worthy to note that the gradle-docker-plugin we would be using has support for Java and Spring boot applications, and there are three plugins available for use – the Remote API Plugin, the Java Application Plugin, and the Spring Boot Application Plugin. For the purpose of this guide, we’ll be making use of the Remote API Plugin. All of these plugins are available on the Gradle Plugin Portal.

CREATE A PROJECT FOLDER

There are a number of ways that Gradle can come in handy for your JVM project, but the most common way is the build automation process, which would be the key thing we would be doing – outlining build tasks for Gradle to process using a Domain Specific Language (DSL), we would be using the Groovy DSL in this guide. And in order to get on with outlining build tasks, you have to decide which project it is you’re running a build process for. If you do not have a project to run a build process for, you can create a new project by starting off with creating a new folder. This new folder would house the whole of the new project.

To do this, head over to your desired directory, create a new folder for your project and switch to the new folder.

mkdir projectname
cd projectname

Note: There are comprehensive guides on the Gradle website that will walk you through how to build applications using Gradle. You can keep following along if you’re working with a Java application, otherwise, jump over to the APPLY THE PLUGIN section.

RUN THE INIT TASK

Now that we have a root folder setup for the project, it is now time to wave our first magic wand, the gradle init command. Now don't fret, what this command does is automatically initialize and create the necessary Gradle files needed to build the new project. With Gradle, you have the opportunity and flexibility of stating the exact things you want Gradle to do for you by using some keywords to state instructions – these instructions are called tasks.

Gradle is shipped with a built-in task, called init which initializes a new Gradle project. This is what Gradle does every time we tell it init. Right there, inside your new project folder (within a terminal console), run the gradle init command. After doing this, Gradle would prompt you to select the project type to generate, enter 2 for application. Next, Gradle asks for the language to implement the project in, enter 3 for Java. Next, choose a build script DSL, select 1 - as we would be using Groovy for this guide. Hit Enter for the rest of the prompts to make use of the default values of the prompts.

gradle init

REVIEW THE PROJECT FILES

Before proceeding, it is essential to know what the above files and folders do. And in order to understand this, kindly proceed to this guide on the [official Gradle website](https://docs.gradle.org/current/samples/sample_building_java_applications_multi_project.ht ml#review_the_project_files).

The parts of these project files that are essential to building a Docker image are the build scripts – the build.gradle files. These build scripts are where you outline your own instructions (tasks) for Gradle, and the main build script to work with here is the build.gradle(.kts) build file in buildSrc – this is where our list of tasks would go into, it is also where we would be adding the open source gradle-docker-plugin we would be working with.

//buildSrc/build.gradle
plugins {
id 'war'
id 'groovy-gradle-plugin'
}
version = '1.0'
sourceCompatibility = 1.7
repositories {
gradlePluginPortal()
}

RUN THE TESTS

In case you’ve written any unit tests for your project files (or want to run the Gradle-generated tests, you can skip to the next step if this doesn’t interest you), the command to run to get an analysis of your test is ./gradlew check. Run this command within a subproject or the subprojects you’d like to test. Test reports would be generated to the {subproject}/buid/reports folder within that specific subproject you have run the ./gradlew check command.

RUN THE APPLICATION

You can check out the new project by running it via the ./gradlew run command, which basically instructs Gradle to run the mainClass property of your project.

BUNDLE THE APPLICATION

It is now time to generate an archive file that we would be building into a Docker image, the basic command to do this is the ./gradlew build command which builds and compiles the whole project and generates both an app.tar and an app.zip file inside the app/build/distributions folder.

PUBLISH A BUILD SCAN

There is an optional flag that can be appended to the above command, the --scan flag. With this flag, you get the opportunity to get more analysis about the build process of your project. So running ./gradlew build --scan would generate a URL at the end of the build processes, this URL is a link to the remote location of the details of your build process – hence, the build scan.

APPLY THE PLUGIN

Now that we have a build of our project, the very next step is to apply the gradle-docker-plugin to the content of our project. To do this, create a new file titled docker.gradle inside the gradle folder within your project (this gradle folder houses the wrapper folder for gradle-wrappers). Paste the following code inside gradle/docker.gradle.

//gradle/docker.gradle
buildscript {
//Access to the plugin portal
repositories {
gradlePluginPortal()
}

//Plugin addition that helps Gradle interacting with Docker
dependencies {
classpath 'com.bmuschko:gradle-docker-plugin:6.7.0'
}
}
apply plugin: com.bmuschko.gradle.docker.DockerRemoteApiPlugin

Then go inside buildSrc/build.gradle, and paste in the script below

//buildSrc/build.gradle
//other scripts
apply from: 'gradle/docker.gradle'

In order to make use of gradle-docker-plugin, you must add Gradle’s Plugin Portal as a repository inside the repositories block, this will grant you access to all community plugins in the Gradle’s plugin portal – including the gradle-docker-plugin. As you can observe, in order to use a plugin, you are required to declare this needed plugin as a dependency in the dependencies {} script block.

CONFIGURE THE PLUGIN

In order to use Docker effectively with the plugin, it is important to provide the credentials needed to host the image we’ll be publishing to the Docker Hub registry. These credentials can be provided as project properties within the gradle.properties file in the home directory for your current user, alternatively, the credentials can be provided as environment variables.

To do this, modify the gradle/docker.gradle file to reflect the script below

buildscript {
//repositories {…}
//dependencies {…}
docker {
newProject {

maintainer = 'YOUR_NAME "YOUR_EMAIL"'
}
registryCredentials {
username = getCredential('DOCKER_USERNAME', 'docker.username')
password = getCredential('DOCKER_PASSWORD', 'docker.password')
email = getCredential('DOCKER_EMAIL', 'docker.email')
}
}
//apply plugin:[…]
String getCredential(String envVar, String sysProp) {
System.getenv(envVar) ?: project.findProperty(sysProp)
}
}

The plugin has been added to the project, and Docker has been configured, what next? I hear you ask, well, it is time to outline the specifics of what tasks we need Gradle to perform. For building our Docker image, we need to run the following tasks listed below sequentially.

  • Create a Dockerfile
  • Build an image using the Dockerfile
  • Push the image to a Docker registry

CREATE A DOCKERFILE

So, we’ll be kicking off by creating a Dockerfile – which is essentially generating a Dockerfile using of the methods provided by the Dockerfile task, this task is also helpful in populating the Dockerfile. To do this, modify the gradle/docker.gradle file to reflect the script below

buildscript {
//repositories {…}
//dependencies {…}
//docker {…}

import com.bmuschko.gradle.docker.tasks.image.Dockerfile

task dockerCreateDockerfile(type: Dockerfile) {
group = 'Docker'
destFile = project.file('build/docker/Dockerfile')
from 'dockerfile/java:openjdk-7-jre'
maintainer 'YOUR_NAME "YOUR_EMAIL"'
copyFile war.archiveName, '/app/projectname.war'
entryPoint 'java'
defaultCommand '-jar', '/app/projectname.war'
exposePort 5701
runCommand 'apk --update --no-cache add curl'
}
task dockerSyncBuildContext(type: Sync) {
dependsOn assemble
from tar.archivePath
into dockerCreateDockerfile.destFile.parentFile
}
dockerCreateDockerfile.dependsOn dockerSyncBuildContext
//apply plugin:[…]
//String getCredential(…) {…}
}

By running the above task, the dockerCreateDockerfile method will produce Dockerfile instructions in the build/docker/Dockerfile file similar to the ones listed as statements within the dockerCreateDockerfile method. These instructions tell Docker to do the following; to copy the built WAR file when creating the Docker image, then the application’s main class should be automatically executed with the java command -when starting the image inside of a Docker container. And once the application is up and running, the container should expose the application’s functionality through the open port 5701.

BUILD AN IMAGE USING THE DOCKERFILE

Now it is time to build an image using the Dockerfile we created. Before proceeding, ensure your Docker environment is up and running, and your environment variables (or project settings) have been modified to reflect your Docker credentials. Moving on, we will be using the DockerBuildImage task to take care of all the implementation details necessary to build a Docker image. To do this, modify the gradle/docker.gradle file to reflect the script below

buildscript {
//repositories {…}
//dependencies {…}
//docker {…}

//import com.bmuschko.gradle.docker.tasks.image.Dockerfile
import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage
//task dockerCreateDockerfile(type: Dockerfile) {…}
//task dockerSyncBuildContext(type: Sync) {…}
//dockerCreateDockerfile.dependsOn dockerSyncBuildContext
task dockerBuildImage(type: DockerBuildImage) {
group = 'Docker'
dependsOn dockerCreateDockerfile
inputDir = dockerCreateDockerfile.destFile.parentFile
tag = "bashirk/projectname:$war.version"
}
//apply plugin:[…]
//String getCredential(…) {…}
}

As you might have observed, the DockerBuildImage task takes care of the building of the image, you only need to point the DockerBuildImage task to the location of the Dockerfile and the War file – and, yes, provide a tag. This tag lists a target Docker repository for your project, and also the version number of your project. You might also want to ensure you have built the correct Docker image for your project, to do this you only need to run the command docker images which would show you a list of the images you have built that are ready for deployment.

PUBLISH THE DOCKER IMAGE

We have built the Docker image, and it is high time we publish the image we have built to a public Docker Hub registry. A Docker Hub is basically a resource repository for published Docker images, public or private. To have your image published to a public Docker Hub registry, modify the gradle/docker.gradle file as follows

buildscript {
//repositories {…}
//dependencies {…}
//docker {…}

//import com.bmuschko.gradle.docker.tasks.image.Dockerfile
//import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage
import com.bmuschko.gradle.docker.tasks.image.DockerPushImage

//task dockerCreateDockerfile(type: Dockerfile) {…}
//task dockerSyncBuildContext(type: Sync) {…}
//dockerCreateDockerfile.dependsOn dockerSyncBuildContext
//task dockerBuildImage(type: DockerBuildImage) {…}
task dockerPushImage(type: DockerPushImage) {
group = 'Docker'
dependsOn dockerBuildImage
conventionMapping.imageName = { dockerBuildImage.getTag() }
}
//apply plugin:[…]

//String getCredential(…) {…}
}

Congratulations! You just published a Docker image built with Gradle to a Docker Hub registry.

At this point, you should have a gradle/docker.gradle file like this below

buildscript {
//Access to the plugin portal
repositories {
gradlePluginPortal()
}
//Plugin addition that helps Gradle interacting with Docker
dependencies {
classpath 'com.bmuschko:gradle-docker-plugin:6.7.0'
}
//Docker configuration
docker {

newProject {

maintainer = 'Korede Bashir "bashirkorede@gmail.com"'
}
registryCredentials {
username = getCredential('DOCKER_USERNAME', 'docker.username')
password = getCredential('DOCKER_PASSWORD', 'docker.password')
email = getCredential('DOCKER_EMAIL', 'docker.email')
}
}
//Imports for the necessary task actions
import com.bmuschko.gradle.docker.tasks.image.Dockerfile
import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage
import com.bmuschko.gradle.docker.tasks.image.DockerPushImage
//Task to create Dockerfile
task dockerCreateDockerfile(type: Dockerfile) {

group = 'Docker'
destFile = project.file('build/docker/Dockerfile')
from ‘dockerfile/java:openjdk-7-jre’
maintainer ‘Korede Bashir "bashirkorede@gmail.com"'
copyFile war.archiveName, '/app/projectname.war
entryPoint 'java'
defaultCommand '-jar', '/app/projectname.war
exposePort 5701
runCommand 'apk --update --no-cache add curl'
}
task dockerSyncBuildContext(type: Sync) {
dependsOn assemble
from java.archivePath
into dockerCreateDockerfile.destFile.parentFile
}
dockerCreateDockerfile.dependsOn dockerSyncBuildContext
//Task to build image
task dockerBuildImage(type: DockerBuildImage) {
group = 'Docker'
dependsOn dockerCreateDockerfile
inputDir = dockerCreateDockerfile.destFile.parentFile
tag = "bashirk/projectname:$war.version"
}
//Task to publish image
task dockerPushImage(type: DockerPushImage) {
group = 'Docker'
dependsOn dockerBuildImage
conventionMapping.imageName = { dockerBuildImage.getTag() }
}
apply plugin: com.bmuschko.gradle.docker.DockerRemoteApiPlugin
String getCredential(String envVar, String sysProp) {
System.getenv(envVar) ?: project.findProperty(sysProp)
}
}

SUMMARY

In this guide you learnt how to initialize and build a new project with Gradle, and also learnt to build this new project into a Docker image, and published the Docker image to a public registry.

Now let’s do a recap (TL;DR); the gradle/docker.gradle file will create the Dockerfile, produce the Docker image with the latest changes in the TAR file and push this image to a Docker Hub registry; in doing this, we walked through some steps, which includes how to:

  • Use gradle init to initialize a new Java application
  • Modularize a project into subprojects
  • Run tests and generate test reports
  • Build and bundle the application
  • Apply the plugin for creating Docker images
  • Configure the plugin
  • Create a Dockerfile
  • Build a Docker image
  • Publish a Docker image

NEXT STEPS

It might interest you to learn more details about the Gradle build tool as well as the plugin used with Gradle to generate and build the Docker image in this guide, do check out the following resources: