DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Spring Boot With Kubernetes
  • Containerization and Helm Templatization Best Practices for Microservices in Kubernetes
  • Running a Java App With MySQL in Any Docker Environment
  • Spring Cloud Stream: A Brief Guide

Trending

  • How to Build Scalable Mobile Apps With React Native: A Step-by-Step Guide
  • Scaling Mobile App Performance: How We Cut Screen Load Time From 8s to 2s
  • Chaos Engineering for Microservices
  • Testing SingleStore's MCP Server
  1. DZone
  2. Coding
  3. Frameworks
  4. Two Ways to Dockerize Spring Boot Applications

Two Ways to Dockerize Spring Boot Applications

This article looks at two common options for Dockerizing Spring Boot applications. We will use a simple REST application as a running example.

By 
Taruvai Subramaniam user avatar
Taruvai Subramaniam
DZone Core CORE ·
Jul. 07, 20 · Tutorial
Likes (15)
Comment
Save
Tweet
Share
29.2K Views

Join the DZone community and get the full member experience.

Join For Free

Microservices are often built with the Spring Boot framework and deployed with Docker. This paper looks at two common options for Dockerizing Spring Boot applications. Throughout we will use a simple REST application as a running example.

We will use Spring Tool Suite to build the application, though neither the IDE nor the application matter all that much, as long as we have the pom file. We will assume that the reader has minimal knowledge of Docker, though as we shall see one of the options we will discuss, requires no knowledge of Docker. Here then is the REST controller:

Java
 




x
22


 
1
package hello;
2
 
          
3
import org.springframework.boot.SpringApplication;
4
import org.springframework.boot.autoconfigure.SpringBootApplication;
5
import org.springframework.web.bind.annotation.RequestMapping;
6
import org.springframework.web.bind.annotation.RestController;
7
 
          
8
@SpringBootApplication
9
@RestController
10
public class Application {
11
 
          
12
    @RequestMapping("/")
13
    public String home() {
14
        return "Hello from Spring Boot in Docker";
15
    }
16
 
          
17
    public static void main(String[] args) {
18
        SpringApplication.run(Application.class, args);
19
    }
20
 
          
21
}
22
 
          



We build this into a fat jar in the target directory. The simplest way to Dockerize it is to stuff the fat jar into a container:

Dockerfile

Dockerfile
 




xxxxxxxxxx
1


 
1
FROM adoptopenjdk:11-jre-hotspot
2
ARG JAR_FILE=target/*.jar
3
COPY ${JAR_FILE} app.jar
4
ENTRYPOINT ["java","-jar","/app.jar"



This turns out to be a very bad idea. To see why recall that every instruction of the Docker file creates a layer in the image. In this case, our application and all its dependencies are put in one layer. If we keep changing the application then the image is rebuilt from scratch every time even though the dependency jars rarely change. This leads to slow builds.

A better option is to observe the old software design principle and separate what changes from what stays the same. We can do this by putting the dependencies in the bottom layer and the application layer on top. Docker will then cache the dependency layer and every time we change the application and rebuild the image, the dependency layer will be retrieved from cache leading to faster builds.

So much for the throat clearing. For our first option, we consider a very traditional organization where the development team and the build team are separate; the developers don’t know anything about Docker and don’t want to know. The development team builds applications and give it to the build team to manage the builds and deployment.

dev and build team

The fat jar is organized into three parts:

  • Classes used to bootstrap jar loading
  • Your application classes in BOOT-INF/classes
  • Dependencies in BOOT-INF/lib

You can see this by inspecting the jar file (jar tvf app.jar). We can take advantage of this to separate the layers. We can of course extract the jar file and then, in a Dockerfile move and copy the layers. But Spring makes it even easier with layered jars. So, the developers tweak the pom file to enable layers

Dockerfile
 




xxxxxxxxxx
1
21


 
1
<plugins>
2
<plugin>….
3
 
          
4
<configuration>
5
<layers>
6
<enabled>true</enabled>
7
</layers>
8
</configuration>
9
 
          
10
</plugin>
11
</plugins>



The dev team hands over the fat jar they have built to the build team.

We can list the layers

Java
 




xxxxxxxxxx
1
10


 
1
java -Djarmode=layertools -jar app.jar list
2
 
          
3
dependencies
4
 
          
5
spring-boot-loader
6
 
          
7
snapshot-dependencies
8
 
          
9
application
10
 
          



Now the build team can extract and copy the layers of the jar file to layers of the image in a multi-stage docker file (a multi-stage docker file is one with many named build stages)

Dockerfile

Dockerfile
 




xxxxxxxxxx
1
25


 
1
FROM adoptopenjdk:11-jre-hotspot as builder
2
 
          
3
WORKDIR application
4
 
          
5
ARG JAR_FILE=target/*.jar
6
 
          
7
COPY ${JAR_FILE} application.jar
8
 
          
9
RUN java -Djarmode=layertools -jar application.jar extract
10
 
          
11
FROM adoptopenjdk:11-jre-hotspot
12
 
          
13
WORKDIR application
14
 
          
15
COPY application/dependencies/ ./
16
 
          
17
COPY application/spring-boot-loader/ ./
18
 
          
19
COPY application/snapshot-dependencies/ ./
20
 
          
21
#COPY application/resources/ ./
22
 
          
23
COPY application/application/ ./
24
 
          
25
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]



Note that there is nothing in the Dockerfile that is specific to the application, which is why we used the jarlauncher. It will start the image a little slower but no big deal. Also, we assume that there is a local instance of Docker

We can build the image as usual (calling the image “example”)

docker build. -tag example

Dockerfile
 




xxxxxxxxxx
1


 
1
docker images
2
 
          
3
REPOSITORY TAG IMAGE ID CREATED SIZE
4
 
          
5
example latest de7a3bb4889e 7 days ago 243MB



Now run it:

docker run -it -p80:8080 example:latest

And go to localhost in a browser and see that you get “Hello from Spring Boot in Docker”.

The second option is for a more modern organization and organized per the microservices principles. Here the development team itself is responsible for building and deploying the docker image. But the dev team still knows nothing about Docker. We use jib. From the Google team’s description:

“Jib is a fast and simple container image builder that handles all the steps of packaging your application into a container image. It does not require you to write a Dockerfile or have Docker installed, and it is directly integrated into Maven and Gradle—just add the plugin to your build and you'll have your Java application containerized in no time”

pom.xml image

To use jib we modify the pom to insert:

Dockerfile
 




xxxxxxxxxx
1
19


 
1
<plugin>
2
<groupId>com.google.cloud.tools</groupId>
3
<artifactId>jib-maven-plugin</artifactId>
4
<version>2.4.0</version>
5
<configuration>
6
<to>
7
<image>jibexample2</image>
8
</to>
9
</configuration>
10
</plugin>



Now we assume, for this example that the team has a local instance of Docker running although that is not necessary

We have named the image as jibexample2. Now build the project

mvn compile jib:dockerBuild

If you list the docker images (docker images), you will see:

"REPOSITORY TAG IMAGE ID CREATED SIZE"

jibexample2 latest 7b84d5781eca 50 years ago 142MB

That’s it! No Docker file and no knowledge of Docker. Note the size. You can inspect the image via docker inspect and see that the entry point is hello. Application and you can see the layers listed. The layers are a bit different:

  • Classes
  • Resources
  • Project dependencies
  • Snapshot dependencies
  • All other dependencies

The base image is distroless java. Distroless images contain only runtime dependencies. They do not contain package managers, shells, or any other programs you would expect to find in a standard Linux distribution. You can change the base image. Jib figures out the ENTRYPOINT. For comparison here is the Dockerfile implicitly used by Jib.

Dockerfile
 




xxxxxxxxxx
1
23


 
1
# Jib uses distroless java as the default base image
2
 
          
3
FROM gcr.io/distroless/java:latest
4
 
          
5
 
          
6
 
          
7
COPY dependencyJars /app/libs
8
 
          
9
COPY snapshotDependencyJars /app/libs
10
 
          
11
COPY projectDependencyJars /app/libs
12
 
          
13
COPY resources /app/resources
14
 
          
15
COPY classFiles /app/classes
16
 
          
17
 
          
18
 
          
19
# Jib's default entrypoint when container.entrypoint is not set
20
 
          
21
ENTRYPOINT ["java", jib.container.jvmFlags, "-cp", "/app/resources:/app/classes:/app/libs/*", jib.container.mainClass]
22
 
          
23
CMD [jib.container.args]



Also instead of jib:dockerBuild we can use jib:build which will push the image to a remote registry if you provide the credentials. With jib:buildTar you can save the image as a tarball to target/jib-image.tar which you can inspect and import into Docker. There are tons of other configurations for Jib.

Spring Framework application Docker (software) Spring Boot

Opinions expressed by DZone contributors are their own.

Related

  • Spring Boot With Kubernetes
  • Containerization and Helm Templatization Best Practices for Microservices in Kubernetes
  • Running a Java App With MySQL in Any Docker Environment
  • Spring Cloud Stream: A Brief Guide

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!