Docker With Spring Boot: Part 2 — Create Images Using Build Tools

Vinod Madubashana
Javarevisited
Published in
6 min readFeb 14, 2024
Photo by Rubaitul Azad on Unsplash

In part one of this series, I explained how to use docker files to create images. Although this is the most used option because of its flexibility, it requires some care and some good knowledge of docker to create more secure performant, and up-to-date small images. What if there is a way we can declaratively create images that follow the industry’s best practices rather than manually creating docker files and managing them? That’s what I’m going to talk about in this part, how to use the build tools maven and gradle to create the docker images.

All the examples are available in this GitHub repository.

spring-boot maven plugin and bootBuildImage gradle task

It is as simple as this, magically it will create your docker image.

# For maven
./mvnw spring-boot:build-image
# For gradle
./gradlew bootBuildImage --imageName=gradle-task-build

Both Maven and Gradle builds use technology called cloud native buildpacks which can create reproducible docker images without writing docker files. This is not a spring boot or java specific technique. You can use buildpacks to dockerize almost all of your projects. This is a specification and the spring boot uses the Paketo implementation. This is a border topic that you can investigate if needed. Please read this concept introduction at https://buildpacks.io/docs/concepts/. The brief high-level explanations available in this concepts section are enough for the majority of developers. Check the resources at the end to get more understanding about this technology if you are interested.

Advantages of using buildpacks

  • Don’t need to be a docker expert to create performant and secure docker images
  • Sensible defaults and options to customize
  • It can create reproducible docker images, which means it guarantees to creation same image for the same input. It is done by removing changing metadata like timestamps. If you inspect you will see the creation date of the image is very old like 44 years ago😁. Learn more about reproducible builds here.
  • One cool thing you can do with buildpacks is change the base image without changing the other layers which is called a rebase operation. This can save you a lot of manual effort if you need to update your base images due to security vulnerabilities.

If you are using the spring boot version earlier than 3.2 check this migration guide to update the builder images to the latest version.

Google Jib

This is also can be used to create images without writing docker images. Both gradle and maven build tool support are available. Not like buildpacks this is Java specific solution. The other characteristics are similar to cloud-native buildpacks. The base images are chosen from Google distroless images to create small and secure images.

The configuration details for Maven can be found here and for Gradle here. Examples are available in my GitHub repository which are with some minimal configurations. At the time of writing jib does not support Java 21 automatically, hence need to define the base image in the plugins. (see below configs for pom.xml and build.gradle)

<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<to>
<image>jib-maven</image>
</to>
<from>
<image>eclipse-temurin:21-jre</image>
</from>
</configuration>
</plugin>
plugins {
id 'com.google.cloud.tools.jib' version '3.4.0'
}
jib.to.image = 'jib-gradle'
jib.from.image = 'eclipse-temurin:21-jre'

If you are using a docker desktop in Windows, you might need to add the docker login info to the config.json file located at your home(users/<you_user_name>)/.docker directory. Simply remove the credsStore line in this file and then perform the docker login command in a terminal and provide your docker hub credentials which update this file with auth details requiring jib to pull base images.

fabric8io/docker-maven-plugin

This is a Maven plugin. You can find the full documentation at https://dmp.fabric8.io/. This is like writing a docker file using a Maven plugin. The advantage is this plugin has different goals like build, run, stop, and push to deal with docker images directly without using a docker client. See a basic example of plugin usage. (Note: if you don’t need to create a docker image when running mvn package remove the executions section below example)

<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.43.4</version>
<executions>
<execution>
<id>docker:build</id>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
<configuration>
<verbose>true</verbose>
<images>
<image>
<name>fabric8-dmp:v2</name>
<build>
<from>eclipse-temurin:17</from>
<assembly>
<descriptorRef>artifact</descriptorRef>
</assembly>
<entryPoint>
<exec>
<arg>java</arg>
<arg>-jar</arg>
<arg>/maven/${project.build.finalName}.${project.packaging}</arg>
</exec>
</entryPoint>
</build>
<run>
<ports>
<port>9050:8080</port>
</ports>
</run>
</image>
</images>
</configuration>
</plugin>

This plugin creates a docker file under the hood. You can see the output in the target directory.

FROM eclipse-temurin:21
COPY maven /maven/
ENTRYPOINT ["java","-jar","/maven/spring-boot-docker-1.0.0.jar"]

My personal preference is to use Dockerfile rather than using this plugin.

Gradle docker plugin

This is kind of similar Gradle version of the docker maven plugin. The complete documentation can be found here. This also has tasks like build and push, but by default lacks many operations like starting the container. But we can create some of them as new tasks in Gradle. See the example configurations

plugins {
id 'com.bmuschko.docker-spring-boot-application' version '9.4.0'
id 'com.bmuschko.docker-remote-api' version '9.4.0'
}
docker {
springBootApplication {
baseImage = 'eclipse-temurin:21'
ports = [8080]
images = ['gradle-docker']
}
}
import com.bmuschko.gradle.docker.tasks.container.*task createContainer(type: DockerCreateContainer) {
dependsOn
targetImageId dockerBuildImage.getImageId()
hostConfig.portBindings = ['8080:8080']
hostConfig.autoRemove = true
}
task startContainer(type: DockerStartContainer) {
dependsOn createContainer
targetContainerId createContainer.getContainerId()
}

This plugin also creates a docker file under the hood. You can see the output in the build directory.

FROM eclipse-temurin:21
LABEL maintainer=Vinod
WORKDIR /app
COPY libs libs/
COPY resources resources/
COPY classes classes/
ENTRYPOINT ["java", "-cp", "/app/resources:/app/classes:/app/libs/*", "org.example.dockerfileexample.DockerFileExampleApplication"]
EXPOSE 8080

Summary

  • Cloud-native buildpacks is the modern way of creating images which is not limited to Java applications. It is a declarative approach that does not require writing a docker file. Spring Boot has built-in support for buildpacks with Spring Boot Maven and Gradle plugins.
  • Google Jib is another option that does not use docker files, but this is a Java-specific technique.
  • Maven and Gradle docker plugins allow to specify docker build details within the plugin without writing a docker file, but under the hood, it creates a docker file.
  • Basic examples for each option are available in this GitHub Repository.
  • If you plan to use any of these approaches in your production system, please study more about the configuration options to fine-tune. I have attached some resources in the below resource section.
  • The main goal of this article is not to deep dive into all the options, it is to give you some choices to consider for your next or existing projects.

In the next part of this series let’s examine the created images. Until then happy coding!!!!

Resources

--

--

Vinod Madubashana
Javarevisited

Full-stack Developer, Java, Spring Boot, React, Angular, AWS ...