When Docker Meets Java
When Docker Meets Java
Meets Java
A Practical Guide to Docker for Java
and Spring Boot Applications
—
Ashish Choudhary
When Docker
Meets Java
A Practical Guide to Docker
for Java and Spring Boot
Applications
Ashish Choudhary
When Docker Meets Java: A Practical Guide to Docker for Java and Spring
Boot Applications
Ashish Choudhary
Pune, Maharashtra, India
v
Table of Contents
vi
Table of Contents
vii
Table of Contents
viii
Table of Contents
ix
Table of Contents
Img��������������������������������������������������������������������������������������������������������������������169
Why img?�����������������������������������������������������������������������������������������������������169
Features of img�������������������������������������������������������������������������������������������170
Using img to Build and Push Docker Images�����������������������������������������������170
Summary����������������������������������������������������������������������������������������������������������171
x
Table of Contents
xi
Table of Contents
Index�������������������������������������������������������������������������������������������������233
xii
About the Author
Ashish Choudhary is a senior software
engineer and published author. He has over
14 years of experience in the IT industry. He
has experience in designing, developing, and
deploying web applications. His technical
expertise includes Java, Spring Boot, Docker,
Kubernetes, IMDG, Distributed Systems,
Microservices, DevOps, and the Cloud. He is
an active blogger and technical writer. He has
delivered talks at renowned conferences like GitHub Satellite India and
Fosdem. He is a strong advocate of open source technologies. He has been
contributing to various open source projects for quite some time. Ashish
believes in continuous learning and knowledge sharing.
xiii
About the Technical Reviewer
Anant Chowdhary is a software engineer
working on bringing AI-based dubbing to
videos. Having completed a master’s in
Computer Science with a focus on Machine
Learning and Distributed Systems, Anant
is a technology professional with extensive
experience in designing and optimizing
complex systems. He is deeply interested
in the transformative potential of emerging
technologies, particularly AI and automation, and how these innovations
are reshaping industries, society, and the way we interact with the world.
Passionate about exploring the intersection of technology and human
behavior, he is committed to understanding the broader implications of
digital advancements on both individuals and communities. Working on
planet scale systems, he has a wealth of experience in Distributed Systems
and Applied Machine Learning.
xv
CHAPTER 1
Overview of Containers
As a child, I spent so much time using Lego to build things, all the while
thinking that there was no way these stupidly simple and standardized 2x4
bricks could be the origin of all the awesome possibilities. Little did I know
that those colorful blocks were instilling in me the fundamental principle
of modern software development. Just as Lego reinvented play, containers
have fundamentally changed how we develop, package, and deploy
applications. Now imagine your software as if it were a Lego construction.
Containers are individual building blocks, standardized and endlessly
combinable, each one representing a containerized component.
Just like the toy bricks, containers provide a standard way to bundle
applications for portability across any compatible system where they run.
Need to scale up? Simple: add more “bricks.” Want to update a feature?
Pop out one container and snap in another, without toppling the whole
tower. Lego is magical in its modularity and flexibility: easily built, broken
apart, and rebuilt. In software development, containers bring the same
agility to software development. They isolate applications and their
dependencies, just like individual Lego bricks are self-contained units.
Such isolation ensures that just like a red 2×4 brick makes no difference
whether it is in a castle or a spaceship, your application will be running
identically whether it is on your laptop or inside a cloud data center.
In this chapter, we put the pieces of container technology together and
explore how those digital building blocks have constructed a new era in
computing. At the end of this, you’re going to see how containers are the
doors to innovation: making developers able to build, share, and deploy
their digital creations with unimaginable ease and creativity.
© Ashish Choudhary 2025 1
A. Choudhary, When Docker Meets Java, https://doi.org/10.1007/979-8-8688-1300-9_1
Chapter 1 Overview of Containers
A Bit of History
In 2010, a small startup called dotCloud was struggling in the competitive
Platform-as-a-Service market. What they didn’t know was that they were
actually about to change the tech world.
Headed by Solomon Hykes, the dotCloud team developed an in-house
tool for managing Linux containers that were meant to improve their
system but soon became so much more.
Linux Containers are a kind of operating system-level virtualization,
running multiple independent Linux environments on one machine.
LXC shares the host machine’s kernel with each one, which gives a leaner
alternative to a virtual machine, yet maintains process, file system, and
network space isolation.
LXC utilizes cgroups (control groups) and namespaces to manage and
limit resources, producing a virtualization experience similar to running
natively on an underlying system without the overhead of a full hypervisor.
Hykes introduced Docker at PyCon in March 2013. He received an
immediate and enthusiastic response from the developers because
Docker suggested a solution for how to more easily create, deploy, and run
applications consistently in any environment.
A key innovation in Docker was its capability to bundle an application
and its dependencies in a standardized unit, that is, containing libraries,
dependencies, configuration files, and runtime environment—in a
consistent format, which is also called a container. This alone solved the
age-old developer headache: “It works on my machine!”
As Docker became popular, dotCloud shifted direction. They renamed
it to Docker, Inc., and now focused exclusively on creating the Docker
ecosystem. The project gained momentum very quickly:
Docker Hub was launched in 2014, providing a central location for
images of containers.
In 2015, Docker Swarm followed with the native orchestration of
containers.
2
Chapter 1 Overview of Containers
Definition of Containers
Let’s begin by exploring a formal definition of containers before
going deeper.
Docker’s Definition
Docker defines container as follows:
A container is a standard unit of software that packages up code and
all its dependencies, so the application runs quickly and reliably from
one computing environment to another. A Docker container image is a
lightweight, standalone, executable package of software that includes
everything needed to run an application: code, runtime, system tools, system
libraries, and settings.
3
Chapter 1 Overview of Containers
Understanding Containers
This definition provides a more comprehensive and easily understandable
explanation. For a Java application, the container will encompass the
base image, JRE (Java Runtime Environment), application code, and other
necessary dependencies for its execution.
Let’s further illustrate this concept with an additional example. In Java,
a Class serves as a blueprint or template defining the state and behavior
of objects. By utilizing this template, we can create multiple instances of
the class. Similarly, a container image is a template from which numerous
container instances can be generated.
Containers can be compared to black boxes without their internal
details being visible. Each container possesses its own IP address,
hostname, and disk. While we will explore the benefits of containers in
future lessons, isolation is one of their notable advantages. Consider
running two applications requiring distinct versions of Java or incompatible
tools and libraries. Achieving this on virtual machines (VMs) would be
challenging, resulting in resource wastage. However, such isolation is
inherent with containers, and running multiple applications with different
requirements becomes feasible.
4
Chapter 1 Overview of Containers
5
Chapter 1 Overview of Containers
6
Chapter 1 Overview of Containers
Portability
Portability in computing refers to the capability of executing a computer
program or software on an operating system different from the one it was
initially designed for. Due to their inherent portability, containers can
be utilized across various platforms. They are compatible with Linux,
Windows, macOS, and numerous other widely used operating systems,
ensuring consistent behavior on virtual machines, physical servers, and
personal laptops.
Resource Utilization
Containers can be launched without booting an entire operating system,
thus reducing resource consumption. We can operate efficiently using
fewer resources and minimize our expenditure associated with cloud
services or data center operations.
Isolation
By running containers on a single server, each container is isolated from all
others thereby ensuring any issue in one specific container does not affect
any other container with the same application being run in it.
Agility
Starts, stops, removals—everything happens swiftly because containers
are lightweight and self-contained. Due to their quick startup and
shutdown times, they are suitable for continuous integration and
7
Chapter 1 Overview of Containers
Easy to Scale
Horizontal scaling of containers becomes much easier by running multiple
identical application instances. For instance, Kubernetes is a container
orchestration tool that can automatically scale containers offering an
advanced approach to containerized applications.
Improved Productivity
Often developers say “It works on my machine” meaning their code
runs well without any issues in their setup. However, it often fails to
work properly in the production environment as per the expectations.
Containers solve this problem by providing predictable environments for
them, so there is no need to bother about such compatibility problems.
Cloud Support
Major cloud platforms such as Amazon Web Services, Azure, and
Google Cloud Platform have embraced containers. In other words, these
platforms have adopted container-based services. This is made possible
by containers being packaged in a standard format such as the Open
Container Initiative (OCI) that enables them to run without any deviation
on several cloud platforms. Hence, we can be assured that our application
will run in the same way regardless of which cloud environment it runs on.
The following diagram demonstrates some important aspects of
containers like their isolation, self-containment, and lightweight design. It
also emphasizes its portability meaning that your app could function with
flexibility over different clouds.
8
Chapter 1 Overview of Containers
9
Chapter 1 Overview of Containers
10
Chapter 1 Overview of Containers
Rise of Docker
Since its introduction in March 2013, Docker has emerged as the
undeniable standard for containerizing application workloads. According
to the 2024 Stack Overflow developer survey, Docker continues to be at
the top of the list among professional developers in the most popular tool
category.
Now, let’s delve into the factors that have captivated developers and
contributed to their fondness for Docker.
11
Chapter 1 Overview of Containers
12
Chapter 1 Overview of Containers
13
Chapter 1 Overview of Containers
14
Chapter 1 Overview of Containers
Summary
In conclusion, Docker has become the standard for containerizing
application workloads due to its efficient utilization of resources,
provides segregation and isolation for enterprise environments, ensures a
consistent development environment, integrates with numerous developer
tools, facilitates deployment of microservices-based architectures, and can
be deployed consistently across hybrid and multi-cloud environments.
15
CHAPTER 2
Docker High-Level
Overview
Learn about docker, its architecture, its limitations,
and how docker works.
Let us now talk about the underlying principle behind how Docker
works: Build Once, Run Anywhere(BORA). Consider this: two developers
are working on the same application, and they want to run the application
on their local machines to speed up the development process. Developer
A finally got the application running on their workstation and shared the
steps they took with Developer B. When Developer B followed these steps,
they couldn’t get the application up and running easily.
Why did developer B encounter difficulties when trying to run the
application?
Well, there could be multiple answers to this but it could be possible
that Developer A unintentionally omitted crucial instructions, such as
environment variables to run the application.
Remember: This issue is all too common among developers, leading
to frustrating situations where some may assert that “it works on my
machine.” At the same time, it fails to function on other setups.
This is precisely where Docker shines, assuring that if an application
is built using Docker, it will exhibit consistent behavior regardless of the
environment—whether it be development, staging, or production. Docker
eliminates the discrepancies caused by environment-specific variations,
offering a reliable and consistent application execution experience.
Docker Is Not!!!
We know about the features offered by Docker, but it’s essential to
understand its limitations.
18
Chapter 2 Docker High-Level Overview
19
Chapter 2 Docker High-Level Overview
20
Chapter 2 Docker High-Level Overview
The Docker CLI executes all the commands we discussed, while the
Docker daemon performs the corresponding actions.
The following diagram visually represents the interaction between the
Docker CLI, Docker Daemon, Docker REST API, Docker Image Registry,
and Docker Containers, providing an overview of the Docker client/
server architecture and the flow of commands and data between the
components.
21
Chapter 2 Docker High-Level Overview
22
Chapter 2 Docker High-Level Overview
23
Chapter 2 Docker High-Level Overview
• Docker Engine
• Docker CLI
• Docker Compose
• Kubernetes
• Content Trust
• Credential Helper
24
Chapter 2 Docker High-Level Overview
Now, let’s explore some of the critical features Docker Desktop provides.
25
Chapter 2 Docker High-Level Overview
26
Chapter 2 Docker High-Level Overview
• Initiating a container
• Stopping a container
27
Chapter 2 Docker High-Level Overview
• Managing volumes
28
Chapter 2 Docker High-Level Overview
• Choose Run.
29
Chapter 2 Docker High-Level Overview
30
Chapter 2 Docker High-Level Overview
31
Chapter 2 Docker High-Level Overview
32
Chapter 2 Docker High-Level Overview
Dockerfile
Docker relies on a text document named Dockerfile when constructing
a container image. This file encompasses a series of instructions that
define the construction of the Docker image. In Java terms, we can say that
Dockerfile is akin to a Java class definition. It contains instructions on how
to build a Docker image. Just as a Java class specifies how to create objects,
a Dockerfile outlines the steps to construct a Docker image.
For a basic Java/Spring Boot application, the Dockerfile typically
includes the following set of instructions.
FROM openjdk:17
COPY target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
The FROM instruction specifies the base image that serves as the
foundation for our application.
The COPY instruction copies the locally built .jar file generated by the
chosen build tool, such as Maven, Ant, or Gradle, into our container image.
As for the ENTRYPOINT instruction, it designates the default executable
command for our container upon startup. In this example, we aim to run
the .jar file using the java -jar command.
Docker Image
Similar to how a JAR file packages a Java application and its dependencies,
a Docker image encapsulates an application, including its runtime,
libraries, and dependencies. Both serve as self-contained units ready for
deployment. Also, just as JAR files can be distributed and shared easily,
Docker images can be effortlessly distributed and shared, allowing users to
push and pull from Docker registries like Docker Hub and facilitating the
seamless sharing and distribution of applications.
33
Chapter 2 Docker High-Level Overview
Docker CLI
The Docker CLI (Command-Line Interface) is the primary means of
interacting with Docker. Commands issued from the CLI are transmitted to
the Docker daemon using these communication channels. In Java, JShell
allows developers to enter and execute Java code snippets interactively,
and the Docker CLI enables developers to execute commands for
managing Docker components.
Docker Container
A Docker container is like an instance of a Java class. It’s a runnable
environment created from a Docker image, similar to how objects are
created from a class in Java. Each container is isolated and runs its
application or service.
Docker Daemon
Docker daemon acts as the Docker core component, like the central
nervous system. It runs as a background service in the host system and
is responsible for executing the commands—like docker build, docker
pull, and docker run—issued through the Docker CLI. It’s comparable
to a JVM running in the background. It is responsible for running and
managing Docker containers, similar to the way in which the JVM manages
the execution of Java applications.
Docker Hub
Docker Hub is the image repository where we can store, share, and
manage container images. Think of a Docker Hub as a central repository
for storing Docker images, similar to how Maven Central Repository stores
Java libraries. Docker Hub, for example, is like a Maven repository for
Docker images.
34
Chapter 2 Docker High-Level Overview
Docker Compose
As application developers, in most cases we would be dealing with
applications comprising several components, such as a front-end API
and a back-end API. Say, for example, that the application requires extra
features such as an Nginx web server and a database that the back-end
API uses to serve data back to the front-end API. Running and managing
these varied components as separate containers can get very tricky, with
several Docker commands needed to assure the running of the entire
application cohesively. To solve this problem, in comes Docker Compose.
Docker Compose is a tool for running multiple containers, so they all work
together in harmony. This is done through the definition of services using
a docker-compose.yml file, outlining the configuration and dependencies
of various containers needed for an application. It will be possible to
efficiently streamline running and management of the whole application
stack using Docker Compose.
It’s much more of a Java build tool, such as Maven or Gradle. In Maven,
for example, a configuration file named pom.xml holds the configuration
of a project and its dependencies; similarly to Docker, in Docker, there
is also one configuration file, usually docker-compose.yml, that defines
multicontainer Docker applications.
A Sample docker-compose.yml file.
version: '3'
services:
app:
build: .
image: my-java-app
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
35
Chapter 2 Docker High-Level Overview
Summary
The chapter introduces Docker as an open source container management
tool that has revolutionized the way applications are packaged and
deployed. It explains Docker’s core principle of “Build Once, Run
Anywhere” (BORA), which ensures consistent application behavior
across different environments, in contrast to the common “It works on my
machine” problem.
36
Chapter 2 Docker High-Level Overview
37
CHAPTER 3
Up and Running
with Docker
Among the many tools available for application packaging and
deployment, Docker is one of the most important in containerization. One
of the key elements in this process is the Dockerfile, which represents a
blueprint of the configuration, dependencies, and steps followed to build
a Docker image. It consists of several instructions that allow us to build
containers using the docker build command.
Creating a Dockerfile
To commence, let’s start by creating an empty Dockerfile. Remember to
name it with a capital “D,” that is, Dockerfile, without any file extension.
By default, the docker build command looks for a file named “Dockerfile”
(with a capital “D”) in the specified context.
If we name it dockerfile or anything else, we will need to specify the
Docker build file using the -f or --file flag during the build process.
Step 1: Create a new directory on the terminal by running the
command mkdir docker. Navigate to the directory using the command
cd docker.
Step 2: Now run touch Dockerfile, creating an empty
Dockerfile for us.
FROM alpine:latest
RUN apk --no-cache add git
CMD git --version
40
Chapter 3 Up and Running with Docker
41
Chapter 3 Up and Running with Docker
42
Chapter 3 Up and Running with Docker
# Dockerfile
FROM openjdk:17-jdk
WORKDIR /app
COPY target/myapp.jar /app
CMD ["java", "-jar", "myapp.jar"]
Layer caching: Docker uses a layered file system for its images, where
instructions in a Dockerfile are cached as layers to be used in accelerating
subsequent builds.
FROM openjdk:17-jdk
WORKDIR /app
COPY pom.xml /app # Cached if pom.xml doesn't change
RUN mvn dependency:go-offline # Dependencies are cached
COPY src /app/src
RUN mvn package # Rebuilds only if src changes
Order matters: Since the layers are cached, the order in which you
have the instructions in a Dockerfile is significant. If you change an
instruction, all future layers become invalid and need to be rebuilt.
# Inefficient
FROM openjdk:17-jdk
WORKDIR /app
43
Chapter 3 Up and Running with Docker
Multiple base images: Even though one can use only the FROM
instruction with a Dockerfile, we can do that by using multi-stage builds,
making it possible to bring artifacts from different bases into one image.
# Stage 1: Build
FROM maven:3.8-openjdk-17 as builder
WORKDIR /app
COPY . .
RUN mvn clean package -DskipTests
# Stage 2: Minimal runtime
FROM openjdk:17-jre
WORKDIR /app
COPY --from=builder /app/target/myapp.jar /app
CMD ["java", "-jar", "myapp.jar"]
44
Chapter 3 Up and Running with Docker
FROM openjdk:17-jdk
LABEL maintainer="[email protected]"
LABEL version="1.0.0"
LABEL description="Java Spring Boot application"
FROM openjdk:17-jdk
ARG API_KEY
ENV API_KEY=${API_KEY}
CMD ["java", "-jar", "myapp.jar"]
45
Chapter 3 Up and Running with Docker
Example
Let’s follow a step-by-step coding example to demonstrate building, tagging,
and pushing a Docker image for a simple Java application to DockerHub.
Step 1. Directory setup: Assume we have a basic Java application with
the following file structure:
my-java-app/
├── src/
│ └── Main.java
└── Dockerfile
FROM eclipse-temurin:17-jdk-jammy
COPY ./src /app
WORKDIR /app
RUN javac Main.java
CMD ["java","Main"]
This Dockerfile uses the official OpenJDK 17 image as the base, copies
the Main.java file into the image, compiles it, and finally sets the command
to run the compiled Java application.
Step 4. Build the docker image: Open the terminal or command
prompt, and ensure we are inside the directory having Dockerfile on the
terminal and type the following command.
This command will build the Docker image with the tag (by using
the -t flag) my-java-app:1.0 as the build context. Setting the image name
and tag while building our image is good practice. The . (dot) at the end
of the command indicates to Docker that Dockerfile is present in the
current working directory.
Step 5. Verify the built docker image: To verify that the Docker image
was built successfully, run the following command:
$ docker images
We should see the my-java-app image with the 1.0 tag listed among our
local Docker images.
Step 6. Tag the docker image for dockerhub: Now, we’ll tag the
Docker image to prepare it for pushing to DockerHub:
47
Chapter 3 Up and Running with Docker
Here’s a simplified diagram that outlines the Docker image build process:
48
Chapter 3 Up and Running with Docker
In this flow:
49
Chapter 3 Up and Running with Docker
50
Chapter 3 Up and Running with Docker
51
Chapter 3 Up and Running with Docker
52
Chapter 3 Up and Running with Docker
5. Latest tag: Using the latest tag for the latest build
is a convenience, but not the best practice in a
production environment due to the ambiguity it has:
53
Chapter 3 Up and Running with Docker
54
Chapter 3 Up and Running with Docker
Now, we’ll use the docker push command to push Docker image to
DockerHub:
55
Chapter 3 Up and Running with Docker
56
Chapter 3 Up and Running with Docker
Common Pitfalls
There are several things to watch out for, or common pitfalls when
developers are running a Docker image to achieve a smooth and trouble-
free experience.
• Port mapping: Make sure your port mapping is
accurate so we do not run into any inaccessible
applications.
57
Chapter 3 Up and Running with Docker
59
Chapter 3 Up and Running with Docker
$ docker ps
This command will display the ports the container exposes to the host
system. Verify that the required ports are correctly mapped and accessible.
Step 6. Analyze docker logs: Review the container’s logs to identify
any errors or issues:
60
Chapter 3 Up and Running with Docker
Docker initiates a process within the container and gathers the output
streams from this process as logs. By default, Docker uses the json-file
driver, which writes these logs in JSON format to a file.
Here is an image illustrating the interaction between the application,
the output streams, and Docker.
61
Chapter 3 Up and Running with Docker
62
Chapter 3 Up and Running with Docker
Summary
This chapter gives a comprehensive guide to understanding and working
with Docker, focusing on Dockerfiles and container management. It
introduces Dockerfiles as blueprints for building container images,
detailing key commands like FROM, RUN, CMD, COPY, and EXPOSE. Best
practices include optimizing image size, managing secrets, and using
multi-stage builds.
The chapter explains the image-building process, tagging strategies,
and steps for pushing, pulling, and running images. It talks about
debugging, image management, and commands to inspect and clean up
resources.
The chapter also highlights common mistakes such as wrong port
mapping, resource mismanagement, and security oversights and focuses
on scanning images and protection of sensitive data.
In the end, the chapter summarizes the benefits of using Docker, such
as having consistent environments, simplified distribution, scalability, and
resource usage.
63
CHAPTER 4
Learning Advanced
Docker Concepts
Discover how Docker containers communicate and explore various
Docker networking drivers. Learn how to enable data persistence with
containers using docker volumes. Know how to create, configure, and
manage multicontainer applications with Docker.
• bridge
• host
• none
However, since these might only suit some context, we’ll also delve into
user-defined networks like overlay and macvlan. Let’s examine each in
more detail.
66
Chapter 4 Learning Advanced Docker Concepts
Bridge Driver
This serves as the default driver. When we initiate Docker, a bridge network
is established, and all newly launched containers will automatically
connect to this default bridge network.
We can employ this when we want isolated containers to communicate
internally. Given the segregation of containers, the bridge network
effectively resolves port conflicts. It resolves port conflicts by providing
each container with its internal IP address within the bridge network’s
subnet. Containers within the same bridge network can interact, while
Docker utilizes iptables on the host machine to restrict access beyond
the bridge.
Following is an example describing how the bridge network driver
operates:
67
Chapter 4 Learning Advanced Docker Concepts
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED
STATUS PORTS NAMES
8a6464e82c4u busybox "/bin/sh" 6 seconds ago
Up 6 seconds container2
9bea14032749 busybox "/bin/sh" 28 seconds
ago Up 28 seconds container1
68
Chapter 4 Learning Advanced Docker Concepts
Host Driver
As the name implies, the host driver leverages the host machine’s
networking. This removes network isolation between the container and
the host machine where Docker operates.
--network host: Uses the host network, meaning the container shares
the host’s network namespace. The container will directly bind to the
host’s ports without Docker’s network isolation.
For example, the official Nginx image listens on port 80 by default;
when a container bound to port 80 employs host networking, the
container’s application is accessible on port 80 via the host’s IP address.
So in this case, If the host machine’s port 80 is not already in use, you can
access Nginx at http://localhost:80/.
This driver is Linux-specific and isn’t available on Docker desktop
installations. We can leverage it if we want to depend on the host
machine’s networking rather than Docker’s.
69
Chapter 4 Learning Advanced Docker Concepts
None Driver
This driver avoids attaching containers to any network. Containers
remain cut off from the external network and communication with other
containers.
This driver is helpful when we need to deactivate networking on a
container.
70
Chapter 4 Learning Advanced Docker Concepts
To sum up, Docker’s three primary network drivers are bridge, host,
and none. The host driver leverages the host machine’s networking, while
the none driver cuts off containers from the external network. Then there
are user-defined networks like overlay and macvlan, which support multi-
host communication and are often used in environments like Docker
Swarm or Kubernetes.
Docker Volumes
Docker volumes play a pivotal role in efficiently managing data within
containers. First of all, let us understand what Docker volume is. A Docker
volume is just a directory that lives outside of a container’s file system,
yet it is available to the container. It allows data to persist even when
the container is halted or deleted. They enable persistent and shareable
data among containers, effectively separating application data from the
71
Chapter 4 Learning Advanced Docker Concepts
72
Chapter 4 Learning Advanced Docker Concepts
In this diagram:
73
Chapter 4 Learning Advanced Docker Concepts
Volume Inspection
To fully understand a Docker volume, explore its details with the docker
volume inspect command and append the name of your volume. It shows
comprehensive details about the configuration and how the volume is
stored in our host system.
74
Chapter 4 Learning Advanced Docker Concepts
75
Chapter 4 Learning Advanced Docker Concepts
76
Chapter 4 Learning Advanced Docker Concepts
Docker Compose
Understanding Docker Compose
Docker Compose simplifies the management of multicontainer
applications, making it an excellent tool for Java developers. We can
seamlessly orchestrate complex setups by defining services, networks, and
volumes in a single file. Whether working on a Spring Boot application or
any Java project, Docker Compose enhances our development workflow.
With Docker Compose, Java developers can efficiently create, configure,
and manage multicontainer applications.
Docker Compose simplifies the management of multicontainer
applications by defining them in a single docker-compose.yml file. This file
can include services, networks, and volumes, making it a convenient tool
for orchestrating complex setups. Like the Dockerfile, this file should also
be placed at the root of our project repository.
Here’s a simple diagram illustrating the basic structure of a Docker
Compose file.
77
Chapter 4 Learning Advanced Docker Concepts
78
Chapter 4 Learning Advanced Docker Concepts
$ docker-compose --version
79
Chapter 4 Learning Advanced Docker Concepts
version: '3'
services:
app:
build: .
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/mydb
SPRING_DATASOURCE_USERNAME: user
SPRING_DATASOURCE_PASSWORD: password
depends_on:
- db
db:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: mydb
MYSQL_USER: user
MYSQL_PASSWORD: password
80
Chapter 4 Learning Advanced Docker Concepts
version: '3'
services:
app:
build: .
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/mydb
SPRING_DATASOURCE_USERNAME: user
SPRING_DATASOURCE_PASSWORD: password
81
Chapter 4 Learning Advanced Docker Concepts
Envrionment variables
version: '3'
services:
app:
image: openjdk:17
# ...
deploy:
replicas: 3
82
Chapter 4 Learning Advanced Docker Concepts
83
Chapter 4 Learning Advanced Docker Concepts
84
Chapter 4 Learning Advanced Docker Concepts
Summary
This chapter has covered advanced Docker concepts: networking,
volumes, and compose. We have examined network drivers like bridge,
host, none, and user-defined networks and also learned about basic
networking commands.
For Docker volumes, we learned how they can be used for persistence
and sharing between containers. This chapter showed how to create,
list, and inspect volumes, mount them, and manage permissions and
ownership; how to delete volumes was also shown.
We then covered Docker Compose, which manages multicontainer
applications. We also explained the structure of docker-compose.yml files
and topics such as defining services, networking, managing dependencies,
and scaling. The chapter concluded by providing an overview of Docker
Compose support in Spring Boot 3.1, which improves integration and
development workflows. Knowing these features will help in development
and deploying containerized applications.
In the next chapter, we will learn about various base images we can use
for containerizing Java applications.
85
CHAPTER 5
Containerizing
Java Applications
with Dockerfile
This chapter will take a much deeper look at containerizing Java
applications with Docker with a specific focus on Spring Boot. The key
topics will include selection of base image for a Dockerfile and brief intro
to buildpack for containerizing Spring Boot applications.
88
Chapter 5 Containerizing Java Applications with Dockerfile
FROM openjdk:17-jdk
89
Chapter 5 Containerizing Java Applications with Dockerfile
FROM eclipse-temurin:17-jdk
FROM eclipse-temurin:17-alpine
90
Chapter 5 Containerizing Java Applications with Dockerfile
The idea here is that you keep only the stuff relevant to your applications
and get rid of the bloat. Since they are small in size, it makes perfect sense
to use them for cloud use cases because in the cloud you are being charged
heavily for the computing resources.
Consider the following Distroless example for your Java application:
FROM gcr.io/distroless/java:17
91
Chapter 5 Containerizing Java Applications with Dockerfile
Security Considerations
Choose a base image as close as possible to the officially maintained
repositories, and keep updated as often as necessary in your CI/CD
pipeline so you will receive security patches and fixes. Explore scanning
your Docker images for vulnerabilities through tools like Clair or Trivy.
Clair is an open source static analysis tool for container images
that can parse image contents and report vulnerabilities affecting the
container images.
Trivy is another open source security scanner tool that can find
vulnerabilities and misconfigurations across:
• Code repositories
• Binary artifacts
• Container images
• Kubernetes clusters
92
Chapter 5 Containerizing Java Applications with Dockerfile
93
Chapter 5 Containerizing Java Applications with Dockerfile
package com.example.helloworld;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.
SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class HelloWorldApplication {
@RequestMapping("/")
public String home() {
return "Hello World!";
}
public static void main(String[] args) {
SpringApplication.run(HelloWorldApplication.
class, args);
}
}
94
Chapter 5 Containerizing Java Applications with Dockerfile
$ ./mvnw package
$ java -jar target/*.jar
$ curl localhost:8080
Hello World!
FROM eclipse-temurin:17-jdk
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
• The COPY instruction copies the JAR file from the target/
folder to the root of your Docker image.
95
Chapter 5 Containerizing Java Applications with Dockerfile
FROM eclipse-temurin:jdk-17
WORKDIR /app
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:go-offline
$ mvn spring-boot:build-image
96
Chapter 5 Containerizing Java Applications with Dockerfile
$ ./gradlew bootBuildImage
Summary
This chapter focuses on containerizing Spring Boot Java applications
using Docker. It starts by exploring base images, from which all Docker
containers begin: options are diverse, from an official OpenJDK image
down to very lightweight Alpine Linux versions.
Next, we learned about Dockerizing a simple “Hello World” Spring
Boot application: steps to wrap the application in a Docker container. You
also developed some understanding of advanced topics like multi-
stage builds and security recommendations for your Docker images using
Distroless images.
97
Chapter 5 Containerizing Java Applications with Dockerfile
One very notable feature is buildpacks, which Spring Boot 2.3 now
offers. This will allow you to use Docker images without writing a single
Dockerfile: with just a simple command, you are good to go. That makes
the containerization process much easier.
98
CHAPTER 6
Working with
Container Builder
Tools for Java
Applications
This chapter will take a much deeper look at four main tools: Google Jib,
Fabric8 Docker Maven Plugin, Spotify’s Docker-Maven-Plugin, and Cloud-
Native Buildpacks. Each tool approaches Java application containerization
differently, from streamlining Docker image creation to integrating
seamlessly with Maven build processes.
100
Chapter 6 Working with Container Builder Tools for Java Applications
<project>
...
<build>
<plugins>
...
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.3.2</version>
<configuration>
<to>
<image>docker.io/my-docker-id/my-app</image>
</to>
</configuration>
</plugin>
...
</plugins>
</build>
...
</project>
101
Chapter 6 Working with Container Builder Tools for Java Applications
With the Maven Jib plugin configured, building the container image is
as simple as running a Maven command:
plugins {
id 'com.google.cloud.tools.jib' version '2.7.1'
}
jib.to.image = 'my-docker-id/my-app'
Use the following command to create and push an image with Gradle.
./gradlew jib
102
Chapter 6 Working with Container Builder Tools for Java Applications
This distinct layer separation helps Jib to optimize the build process
by breaking down the application into these distinct layers. With changes,
only the layers affected need to be rebuilt and pushed to the registry; other
layers are not affected, making quicker and more efficient container image
updates.
That helps to speed up the builds as well as how the resources get
used. It just makes sure that only the necessary parts are rebuilt and
pushed, which then keeps the size of container images themselves
minimal.
103
Chapter 6 Working with Container Builder Tools for Java Applications
104
Chapter 6 Working with Container Builder Tools for Java Applications
Figure 6-3. Image build process with fabric8 docker maven plugin
105
Chapter 6 Working with Container Builder Tools for Java Applications
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>[LATEST_VERSION]</version>
<configuration>
<!-- Plugin configuration goes here -->
</configuration>
</plugin>
In this example, we’ve added the Fabric8 Docker Maven Plugin to the
build section of the pom.xml file. It specifies the plugin’s group ID, artifact
ID, and version, which should match the latest version available during
our project setup.
Defining Docker image configuration: The Fabric8 Docker Maven
Plugin allows us to define Docker image configurations directly in our pom.
xml. We can specify the base image, exposed ports, environment variables,
etc. Below is a simplified example:
106
Chapter 6 Working with Container Builder Tools for Java Applications
<configuration>
<images>
<image>
<alias>my-app-image</alias>
<name>username/my-app</name>
<build>
<from>openjdk:11-jre-slim</from>
<assembly>
<descriptorRef>artifact</descriptorRef>
</assembly>
</build>
<run>
<ports>
<port>tomcat.port:8080</port>
</ports>
<env>
<SPRING_PROFILES_ACTIVE>production</SPRING_
PROFILES_ACTIVE>
</env>
</run>
</image>
</images>
</configuration>
107
Chapter 6 Working with Container Builder Tools for Java Applications
In your settings.xml:
<servers>
<server>
<id>your.registry.com</id> <!-- Use Docker Hub
ID or your custom registry's ID -->
<username>yourusername</username>
<password>yourpassword</password>
</server>
</servers>
108
Chapter 6 Working with Container Builder Tools for Java Applications
<push>
<registry>your.registry.com</registry>
<serverId>your.registry.com</serverId>
<!-- Matches the ID in settings.xml -->
</push>
<configuration>
<images>
<image>
<!-- Image configuration -->
<name>username/my-app:${project.
version}</name>
<build>
<!-- Build configuration -->
</build>
<push>
<registry>your.registry.com</registry>
<!-- Optional for Docker Hub -->
</push>
</image>
</images>
</configuration>
110
Chapter 6 Working with Container Builder Tools for Java Applications
111
Chapter 6 Working with Container Builder Tools for Java Applications
Getting Started
Using Spotify’s Docker-Maven-Plugin is straightforward:
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>${dockerfile-maven-version}</version>
<executions>
<execution>
<id>default</id>
<goals>
<goal>build</goal>
<goal>push</goal>
</goals>
</execution>
</executions>
<configuration>
<repository>spotify/foobar</repository>
<tag>${project.version}</tag>
<buildArgs>
<JAR_FILE>${project.build.finalName}.jar
</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
112
Chapter 6 Working with Container Builder Tools for Java Applications
113
Chapter 6 Working with Container Builder Tools for Java Applications
Figure 6-4. Image build process with Spotify docker maven plugin
114
Chapter 6 Working with Container Builder Tools for Java Applications
115
Chapter 6 Working with Container Builder Tools for Java Applications
consume significant time, which may not be ideal for developers. Enter
Cloud-Native Buildpack. CNB, like Spring’s autoconfiguration, simplifies
container management to mirror the simplicity Spring Boot brings to our
application.
116
Chapter 6 Working with Container Builder Tools for Java Applications
117
Chapter 6 Working with Container Builder Tools for Java Applications
uses: buildpacks/github-actions/[email protected]
Configuring Buildpack
Spring Boot 2.3.0.M1 introduces native buildpack support for both Maven
and Gradle. This simplifies the process of generating a Docker image for
our application.
118
Chapter 6 Working with Container Builder Tools for Java Applications
119
Chapter 6 Working with Container Builder Tools for Java Applications
• Finally, run:
<project>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-maven-plugin
</artifactId>
120
Chapter 6 Working with Container Builder Tools for Java Applications
<configuration>
<image>
<name>docker.example.com/
library/${project.artifactId}
</name>
<publish>true</publish>
</image>
<docker>
<publishRegistry>
<username>user</username>
<password>secret</password>
<url>https://docker.example.
com/v1/</url>
<email>user@example.
com</email>
</publishRegistry>
</docker>
</configuration>
</plugin>
</plugins>
</build>
</project>
Summary
This chapter explores container builder tools for Java applications,
focusing on Spring Boot. It covers Google Jib, Fabric8 Docker Maven
Plugin, Spotify’s Docker-Maven-Plugin, and Cloud-Native Buildpacks. All
of these tools provide different means through which Java applications
are containerized, ranging from creating a Docker image without
121
Chapter 6 Working with Container Builder Tools for Java Applications
Dockerfile using Jib and buildpack and integrating Docker image build
into the Maven build process. The chapter provides practical examples,
configuration details, and insights into the benefits of each tool. It aims to
help developers choose the right containerization method for their Java
projects.
122
CHAPTER 7
Deploying Docker
Containers Using
GitHub Actions
Containerization is now a cornerstone in application deployment
strategies to run the software in light, consistent, and scalable ways.
Docker for Java applications makes it possible for them to run anywhere,
irrespective of differences between the underlying systems. With GitHub
Actions, developers can automate the building, testing, and deployment of
containers.
124
Chapter 7 Deploying Docker Containers Using GitHub Actions
126
Chapter 7 Deploying Docker Containers Using GitHub Actions
127
Chapter 7 Deploying Docker Containers Using GitHub Actions
The above diagram shows how a Git push event triggers a defined
workflow in the repository, which then controls the execution of jobs and
steps through actions within the GitHub Actions environment.
128
Chapter 7 Deploying Docker Containers Using GitHub Actions
129
Chapter 7 Deploying Docker Containers Using GitHub Actions
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn -B package --file pom.xml
Workflow file
This step runs after the build and executes all unit
tests in the project.
130
Chapter 7 Deploying Docker Containers Using GitHub Actions
6. Workflow completed.
131
Chapter 7 Deploying Docker Containers Using GitHub Actions
Writing a Dockerfile
A Dockerfile is a script with various commands to create a Docker image.
For a Java application, a typical Dockerfile might look something like this:
132
Chapter 7 Deploying Docker Containers Using GitHub Actions
EXPOSE 8080
# Run the jar file
CMD ["java", "-jar", "target/myapp-1.0-SNAPSHOT.jar"]
133
Chapter 7 Deploying Docker Containers Using GitHub Actions
Build with maven: Compiles the Java application and runs any tests.
134
Chapter 7 Deploying Docker Containers Using GitHub Actions
135
Chapter 7 Deploying Docker Containers Using GitHub Actions
136
Chapter 7 Deploying Docker Containers Using GitHub Actions
137
Chapter 7 Deploying Docker Containers Using GitHub Actions
• roles/run.admin
Artifact Registry
• roles/artifactregistry.admin (project or
repository level)
138
Chapter 7 Deploying Docker Containers Using GitHub Actions
139
Chapter 7 Deploying Docker Containers Using GitHub Actions
141
Chapter 7 Deploying Docker Containers Using GitHub Actions
• Click on “Secrets”.
142
Chapter 7 Deploying Docker Containers Using GitHub Actions
Step 5: Once the workflow is configured, any push to the main branch
will trigger the deployment process. You can monitor the progress and
check logs in the “Actions” tab of your repository. Remember to regularly
update your workflow configurations to align with the evolving needs of
your application and team.
.github/actions/java-maven-build
├── action.yml
# .github/actions/java-maven-build/action.yml
name: 'Java Maven Build'
143
Chapter 7 Deploying Docker Containers Using GitHub Actions
name: Java CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
144
Chapter 7 Deploying Docker Containers Using GitHub Actions
145
Chapter 7 Deploying Docker Containers Using GitHub Actions
Summary
This chapter covers automating Java application deployment using
Docker and GitHub Actions. It begins with an overview of GitHub Actions,
explaining how workflows triggered by events like code pushes can
automate tasks such as building, testing, and deploying code. The chapter
then shows how to set up a CI pipeline for Java using Maven, including
caching to speed up builds.
It also explains how to containerize a Java app using Docker and
automate this process with GitHub Actions. Best practices include reusing
workflows, securing sensitive data, optimizing Docker images with multi-
stage builds, and running security tests. These steps streamline and secure
the CI/CD process. This chapter also covers how we can deploy Docker
images to GCP using GitHub Actions as the CI/CD process.
146
CHAPTER 8
Exploring Docker
Alternatives
While Docker has been the go-to solution for containerization, the
container ecosystem has evolved much, introducing a few powerful
alternatives that address some of the pain points in modern development
environments. This chapter goes through four of the most popular Docker
alternatives, Podman, Buildah, Kaniko, and img, each of which offers
unique advantages in different containerization needs. From Podman’s
daemonless architecture and improvements in security compared to
Docker through Buildah-specific image building capability, to a CI/CD-
optimized setup by Kaniko, and through img’s easier container image
construction, these applications represent the emerging wave of container
solutions. Whether it’s security, efficiency, or requirements of specific use
cases, all these Docker alternatives are necessary for understanding how
one should proceed during the journey toward containerization.
Podman
Podman is an open source software under which containers could be
created, managed, and run on any Linux operating system, originally
developed and maintained by Red Hat with functionality quite like Docker.
Setting Up Podman
To install Podman Desktop on a Mac, you have two main methods: using
the .dmg file or Homebrew. Here are the detailed steps for both methods:
148
Chapter 8 Exploring Docker Alternatives
$ podman version
149
Chapter 8 Exploring Docker Alternatives
150
Chapter 8 Exploring Docker Alternatives
151
Chapter 8 Exploring Docker Alternatives
152
Chapter 8 Exploring Docker Alternatives
153
Chapter 8 Exploring Docker Alternatives
154
Chapter 8 Exploring Docker Alternatives
These steps will get Podman up and running on your Mac. Refer to
the official Podman documentation or Mac-specific installation guides for
detailed instructions or troubleshooting.
156
Chapter 8 Exploring Docker Alternatives
This process creates a basic Spring Boot application that can be further
developed or containerized.
157
Chapter 8 Exploring Docker Alternatives
This action opens a menu where we can choose our location for
Containerfile, typically found in the root directory of the demo folder.
Once the Containerfile is selected, you can assign a name to the
container image, such as “my-custom-image.”
158
Chapter 8 Exploring Docker Alternatives
159
Chapter 8 Exploring Docker Alternatives
In the Port Mapping section, ensure that port 8080 of the container
is mapped to port 8080 of the host. You can leave all other settings
unchanged. Then, click Start Container to initiate the containerized
version of your Spring Boot application, as illustrated in the
following image.
160
Chapter 8 Exploring Docker Alternatives
Buildah
Buildah is an open source tool that provides a command-line interface
for creating and managing OCI (Open Container Initiative) compliant
container images. As an alternative to Docker, Buildah is part of the suite
of tools provided by Red Hat, along with Podman and Skopeo, to work with
containers.
161
Chapter 8 Exploring Docker Alternatives
Buildah Features
Here are some critical aspects of Buildah:
Feature Description
Rootless Container Buildah can create container images without requiring any
Image Building access privileges, reducing the risk of privilege escalation
attacks.
Daemonless Buildah operates without a central daemon, minimizing
Architecture system resource usage and simplifying architecture by
treating each operation as a separate process.
Compatibility with Buildah can build images from existing Dockerfiles, easing
Dockerfiles the transition for users familiar with Docker.
Flexibility in Image Users can build images from scratch or using existing
Building images, allowing for greater customization compared to
Docker.
Integration with Buildah integrates well with other tools like Podman
Other Tools for running containers and Skopeo for transferring and
inspecting images.
Fully Scriptable CLI Buildah’s CLI is fully scriptable, making it suitable for use in
build and deployment pipelines.
OCI Images Support Buildah generates images that are fully compatible with
OCI-compliant tools and systems.
162
Chapter 8 Exploring Docker Alternatives
163
Chapter 8 Exploring Docker Alternatives
164
Chapter 8 Exploring Docker Alternatives
Committing your work: Once done with the changes, you can commit
the working container to an image using buildah commit.
Pushing to a registry: Finally, you can push your image to a container
registry with buildah push.
Let’s say you want to create a simple container image with a
web server:
165
Chapter 8 Exploring Docker Alternatives
Kaniko
Docker has become synonymous with creating and managing containers
in containerization. However, building Docker images typically requires
a Docker daemon, which poses challenges in environments where
running a daemon isn’t feasible or secure. This is where Kaniko enters the
picture, offering a solution to build container images in environments like
continuous integration (CI) pipelines without needing a Docker daemon.
Features of Kaniko
Kaniko boasts several features that make it advantageous for building
Docker images:
• No daemon required: Kaniko doesn’t need a Docker
daemon to build an image, reducing the attack surface
and making it safer in shared environments.
166
Chapter 8 Exploring Docker Alternatives
Understanding Kaniko
The Kaniko executor image (i.e., gcr.io/kaniko-project/executor:latest)
builds an image from a Dockerfile and pushes it to a registry. It begins
by extracting the filesystem from the base image specified by the FROM
command in the Dockerfile. The executor then runs the Dockerfile
commands, taking a snapshot of the filesystem in userspace after each
execution. If any changes occur, it appends a new layer of these files to the
base image and updates the image metadata accordingly.
167
Chapter 8 Exploring Docker Alternatives
{
"auths": {
"https://index.docker.io/v1/": {
"username": "yourusername",
"password": "yourpassword"
}
}
}
168
Chapter 8 Exploring Docker Alternatives
Img
In the evolving landscape of containerization, the need for versatile,
secure, and easy-to-use tools for building container images has never been
greater. This is where img comes into play, offering a fresh approach to
image creation in Docker and container technology.
Why img?
img was developed to address several challenges and limitations posed by
traditional Docker image building methods:
169
Chapter 8 Exploring Docker Alternatives
Features of img
img stands out with its distinct features:
170
Chapter 8 Exploring Docker Alternatives
Step 5. Verifying the image: After pushing, check your Docker registry
to ensure the image has been uploaded successfully.
img emerges as a very powerful tool for building Docker and OCI
images, especially suited for environments where security, simplicity,
and integration with the existing pipelines are paramount. This allows
unprivileged, daemonless image creation, which solves key challenges in
the container ecosystem. It is a valuable asset for developers and DevOps
professionals because its adoption can streamline workflows, enhance
security, and efficiently manage container images.
Summary
This chapter presents four alternatives of Docker and points out what each
is useful for in the container ecosystems:
Podman is a standalone alternative for Docker. It supports daemonless
architecture combined with rootless container management. Podman
supports all native Docker commands but adds another feature: pod
management. This chapter deals with the installation of Podman in Mac
systems and then demonstrates it in practice by containerizing a Spring
Boot application.
171
Chapter 8 Exploring Docker Alternatives
172
CHAPTER 9
Building Native
Images with GraalVM
Learn about building lightning-fast cloud Java
applications with GraalVM and Quarkus.
174
Chapter 9 Building Native Images with GraalVM
175
Chapter 9 Building Native Images with GraalVM
Understanding GraalVM
GraalVM is a high-performance polyglot virtual machine developed by Oracle.
It enhances the capabilities of the standard Java Virtual Machine (JVM) by
offering the following features:
176
Chapter 9 Building Native Images with GraalVM
177
Chapter 9 Building Native Images with GraalVM
178
Chapter 9 Building Native Images with GraalVM
179
Chapter 9 Building Native Images with GraalVM
There are two main ways to build a Spring Boot native image
application, and they are:
Using Spring Boot support for cloud-native buildpacks: This method
generates a lightweight container containing a native executable.
180
Chapter 9 Building Native Images with GraalVM
$ gradle bootBuildImage
Then, you can run the app like any other container:
$ gradle nativeCompile
$ target/demo
181
Chapter 9 Building Native Images with GraalVM
$ build/native/nativeCompile/myproject
182
Chapter 9 Building Native Images with GraalVM
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
You can also run your existing test suite in a native image. This is an
efficient way to validate the compatibility of your application.
To run your existing tests in a native image, run the following goal:
When using the Spring Boot Gradle plugin along with the GraalVM
Native Image plugin, AOT test tasks are set up automatically. It's important
to ensure your Gradle build script includes a plugins block that contains
org.graalvm.buildtools.native.
For executing native tests using Gradle, you should utilize the
nativeTest task.
$ gradle nativeTest
183
Chapter 9 Building Native Images with GraalVM
Knowing Quarkus
Quarkus is an open source Java framework designed for Kubernetes, the
widely used container orchestration platform. It optimizes Java specifically
for containers, enabling it to become an effective platform for serverless,
cloud, and Kubernetes environments.
184
Chapter 9 Building Native Images with GraalVM
185
Chapter 9 Building Native Images with GraalVM
186
Chapter 9 Building Native Images with GraalVM
187
Chapter 9 Building Native Images with GraalVM
mvn io.quarkus.platform:quarkus-maven-plugin:3.6.4:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=kubernetes-quickstart \
-Dextensions='resteasy-reactive,kubernetes,jib'
cd kubernetes-quickstart
This will create a new project containing the Kubernetes and Jib
extensions. Furthermore, the following dependencies are added to our
pom.xml file.
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
188
Chapter 9 Building Native Images with GraalVM
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-container-image-jib</artifactId>
</dependency>
./mvnw install
189
Chapter 9 Building Native Images with GraalVM
190
Chapter 9 Building Native Images with GraalVM
Summary
The chapter gave a detailed overview of how to build native images with
GraalVM and integrate them with popular frameworks like Spring Boot.
Although GraalVM native images have significant advantages, such as
faster startup times and lower memory footprint, some limitations limit
support of Java's dynamic features such as reflection.
Then, the discussion moves on to Spring Boot 3 native support which
offers two main ways of building native images: Cloud Native Buildpacks
and GraalVM Native Build Tools.
Last but not least, it covers Quarkus, a Kubernetes-native Java
framework built from the ground up for container environments, focusing
on its features like live coding and support for imperative and reactive
programming. Throughout the chapter, the focus remains on how these
technologies are transforming Java applications to meet the demands
of modern cloud-native architectures, particularly in containerized and
Kubernetes.
191
CHAPTER 10
Testing Java
Applications Using
Testcontainers
Explore the practical approach to building production-
like test environments for Dockerized applications using
Testcontainers
Introduction to Testcontainers
In software development, integration testing is crucial in ensuring that
different parts of an application work together seamlessly. This is where
Testcontainers, a Java library, steps in. Testcontainers provides lightweight,
throwaway instances of common databases, Selenium web browsers, or
anything else that can run in a Docker container.
The library is designed to support our automated integration tests,
providing a higher level of confidence before moving to production. Using
Docker containers, Testcontainers ensure that the application behaves as
expected in an environment that closely mimics production.
194
Chapter 10 Testing Java Applications Using Testcontainers
195
Chapter 10 Testing Java Applications Using Testcontainers
Testcontainers Features
These features make Testcontainers a powerful ally for developers looking
to ensure their applications will work as expected when deployed in a real-
world environment:
• Diverse container support: Offers lightweight,
throwaway instances for various services, including
databases, web browsers, and message brokers.
196
Chapter 10 Testing Java Applications Using Testcontainers
197
Chapter 10 Testing Java Applications Using Testcontainers
Each testing layer serves a different purpose, from quick unit tests to
thorough end-to-end tests, ensuring that your Spring Boot application is
robust and ready for production.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class EmployeeService {
private final EmployeeRepository employeeRepository;
@Autowired
public EmployeeService(EmployeeRepository
employeeRepository) {
this.employeeRepository = employeeRepository;
}
198
Chapter 10 Testing Java Applications Using Testcontainers
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface EmployeeRepository extends
CrudRepository<Employee, Long> {
}
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
@RedisHash("Employee")
public record Employee(@Id Long id, String name, String
position) {}
199
Chapter 10 Testing Java Applications Using Testcontainers
200
Chapter 10 Testing Java Applications Using Testcontainers
@SpringBootTest
public class EmployeeRepositoryIntegrationTest {
@Autowired
private EmployeeRepository employeeRepository;
@Test
public void testEmployeeRepository() {
Employee employee = new Employee("John Doe",
"Developer");
employeeRepository.save(employee);
Optional<Employee> employee = employeeRepository.
findById(employee.getId());
assertTrue(employee.isPresent());
assertEquals(employee.getName(), employee.get().
getName());
}
}
application-test.properties:
spring.redis.host=localhost
spring.redis.port=6379
201
Chapter 10 Testing Java Applications Using Testcontainers
202
Chapter 10 Testing Java Applications Using Testcontainers
Dependencies Setup
First, make sure the required dependencies are included in our Maven or
Gradle setup is crucial:
For Maven:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
203
Chapter 10 Testing Java Applications Using Testcontainers
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
For Gradle:
dependencies {
testImplementation 'org.springframework.boot:spring-boot-
starter-test'
testImplementation 'org.springframework.boot:spring-boot-
testcontainers'
testImplementation 'org.testcontainers:junit-jupiter'
}
204
Chapter 10 Testing Java Applications Using Testcontainers
@Testcontainers
@SpringBootTest
class DemoApplicationTests {
}
Container Initialization
Create container instances in your test classes using Testcontainers’
utilities. For example, if you want to spin up an in-memory Redis cache
instance:
@Testcontainers
@SpringBootTest
class DemoApplicationTests {
@Container
@ServiceConnection
static RedisContainer container = new
RedisContainer(RedisContainer.DEFAULT_IMAGE_NAME);
@Test
void myTest() {
System.out.println(container.isRunning());
System.out.println(container.getRedisURI());
}
}
205
Chapter 10 Testing Java Applications Using Testcontainers
206
Chapter 10 Testing Java Applications Using Testcontainers
@SpringBootTest
@Testcontainers
public class EmployeeRepositoryIntegrationTest {
@Container
@ServiceConnection
static RedisContainer redis = new
RedisContainer(DockerImageName.parse("redis:latest"));
@Autowired
private EmployeeRepository employeeRepository;
@Test
public void testEmployeeRepository() {
Employee employee = new Employee(1L, "John Doe",
"Developer");
employeeRepository.save(employee);
Optional<Employee> foundEmployee = employeeRepository.
findById(employee.getId());
assertTrue(foundEmployee.isPresent(), "Employee should
be found");
assertEquals(employee.getName(), foundEmployee.get().
getName(), "Employee names should match");
}
}
207
Chapter 10 Testing Java Applications Using Testcontainers
Summary
This chapter covers the basics of Testcontainers and using it with Java, with
the focus on Spring Boot integration testing. It first explains what are the
core concepts behind Testcontainers, its necessity, and its major features:
support for diverse containers, integration with JUnit, and automatic
resource management. Then, it goes into the implementation details,
focusing on testing strategies for Spring Boot applications at unit and
integration levels.
This chapter provides practical examples of setting up and using
Testcontainers and demonstrates integration with Redis containers by
detailing the required configuration steps. The main emphasis throughout
the chapter is on the advantages of the Testcontainers way of doing
things compared to traditional ways of testing—namely, in obtaining
environment parity, portability, and efficient resource utilization. Practical
implementation guidelines are also covered, such as dependency setup
and proper annotation usage, to provide a complete understanding for
developers on how to harness Testcontainers to have more reliable and
maintainable integration tests.
208
CHAPTER 11
210
Chapter 11 Docker Best Practices for Java Developers
Best Practices
Tips on organizing stages: Name each stage for clarity (e.g., FROM
maven:3.6.3-jdk-11 as builder). Keep the build stage clean and focus
only on what’s necessary to compile the code.
Minimizing layers and cache usage: Minimize the number of layers
by combining commands where possible. Leverage Docker’s build cache
by organizing commands of least to most likely to change.
Example
Stage 1. Build: Using Maven image, add source code, and run mvn
package.
Stage 2. Runtime: Using the JRE image, copy the JAR file from the
build stage.
211
Chapter 11 Docker Best Practices for Java Developers
Dockerfile structure:
# Build stage
FROM maven:3.6.3-jdk-11 as builder
WORKDIR /app
COPY . .
RUN mvn clean package
# Runtime stage
FROM openjdk:11-jre-slim
COPY --from=builder /app/target/myapp.jar /usr/local/lib/
myapp.jar
ENTRYPOINT ["java","-jar","/usr/local/lib/myapp.jar"]
Multistage builds are crucial for building light and secure Docker
images for Java applications. Separation of the build and runtime
environments can greatly reduce the size of the final image and minimize
the security vulnerabilities that come with large and bloated images.
That is a technique any serious Java developer should find invaluable in
using Docker.
212
Chapter 11 Docker Best Practices for Java Developers
Knowing jlink
• Modules identification: jlink works in terms of
modules. Identify which modules are needed for your
application. Analyze dependencies using tools such
as jdeps.
213
Chapter 11 Docker Best Practices for Java Developers
In this command:
214
Chapter 11 Docker Best Practices for Java Developers
The following diagram shows the critical steps involved in the jlink
process.
215
Chapter 11 Docker Best Practices for Java Developers
216
Chapter 11 Docker Best Practices for Java Developers
217
Chapter 11 Docker Best Practices for Java Developers
Step-by-Step Guide
Using Jlink in Dockerfile
Best Practices
• Minimal module set: Only include the necessary
modules.
218
Chapter 11 Docker Best Practices for Java Developers
Example
• Scenario: A simple Java application that uses HTTP
and JSON processing.
• Dockerfile setup:
• Sample Dockerfile:
# Compile Stage
FROM openjdk:11 as build
WORKDIR /app
COPY . .
RUN javac -d out --module-path lib --module-source-path
src $(find src -name "*.java")
RUN jlink --add-modules java.base,java.net.http,
java.json --output jre
# Final Stage
FROM alpine:latest
COPY --from=build /app/jre /opt/jre
COPY --from=build /app/out /app
ENTRYPOINT ["/opt/jre/bin/java", "-m", "com.myapp/com.
myapp.Main"]
219
Chapter 11 Docker Best Practices for Java Developers
FROM gcr.io/distroless/java17-debian12
COPY target/myapp.jar /app.jar
CMD ["app.jar"]
220
Chapter 11 Docker Best Practices for Java Developers
221
Chapter 11 Docker Best Practices for Java Developers
Best Practices
• Understand your application’s dependencies: Ensure
all runtime dependencies are included in your image.
222
Chapter 11 Docker Best Practices for Java Developers
223
Chapter 11 Docker Best Practices for Java Developers
224
Chapter 11 Docker Best Practices for Java Developers
of both the application and the host system. Regular monitoring and
adjustments based on the application’s behavior are essential to optimal
performance.
225
Chapter 11 Docker Best Practices for Java Developers
Best Practices
• Continuous monitoring: Regularly scan images for
vulnerabilities, even after deployment.
227
Chapter 11 Docker Best Practices for Java Developers
P
ros and Cons
Image Pros Cons Use Case
Type
228
Chapter 11 Docker Best Practices for Java Developers
Best Practices
• Analyze your requirements: Determine whether
your application needs to be compiled or if it’s only
being run.
Example
• Maven image:
229
Chapter 11 Docker Best Practices for Java Developers
• Dockerfile example:
• JDK image:
• Dockerfile example:
FROM openjdk:11-jdk
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp
RUN javac Main.java
• JRE image:
• Dockerfile example:
FROM openjdk:11-jre-slim
COPY --from=build /usr/src/app/target/app.jar /usr/app/
ENTRYPOINT ["java", "-jar", "/usr/app/app.jar"]
230
Chapter 11 Docker Best Practices for Java Developers
Summary
This chapter is a comprehensive review of Docker best practices that
are vital for Java developers in today’s containerized environments. The
chapter starts with multistage builds, which are advanced techniques for
creating lean and secure Docker images. Isolating the build environment
from the runtime environment greatly reduces the size of the final image
but still keeps all the functionality needed; it shows how to structure
Dockerfiles and handle artifacts between stages.
The discussion then moves on to Java runtime optimization using jlink,
a powerful tool introduced in Java 9. This section shows developers how to
create custom runtime images containing only the modules necessary for
their applications. Not only does this targeted approach reduce container
size, but it also improves security by minimizing the potential attack
surface; it provides practical examples and best practices for module
selection in real-world scenarios.
Moving on, the chapter on distroless base images covers the minimal
container image containing just the application and its dependencies
required at runtime. This proves to be very valuable for improving security
and efficiency in removing unnecessary components, which results in a
smaller, more secure, and easier-to-maintain and deploy container.
The chapter then goes into detail on JVM arguments and resource
management, providing deep insight into the optimization of memory
usage, CPU allocation, and garbage collection settings. The extensive
coverage helps developers realize how resource allocation can be
balanced for optimal performance and common pitfalls in containerized
Java applications. The guidance provided ensures that applications run
efficiently within their containerized environments while maintaining
stability and reliability.
231
Chapter 11 Docker Best Practices for Java Developers
232
Index
A Cloud computing, 9, 17, 97
Cloud-native buildpacks
Abstraction, 19, 83
(CNBs), 180
addEmployee method, 199
autoconfiguration, 115, 116
add-modules, 215
configuration, 118–121
Agility, 1, 7–8, 10
features, 116–118
Ahead-of-time (AOT), 177–179
spring-boot-starter-
Alpine Linux image, 90
data-jpa, 115
Amazon ECS, 19, 20
top-tier OCI, 116
application-test.properties file, 202
Collaboration, 26, 50, 53, 186
Autoconfiguration, 115, 116, 206
Command-line interface (CLI), 20,
Azure Kubernetes Service (AKS), 20
22, 34, 161
Communication, 34, 65, 66,
B 69–71, 76
Bridge driver, 67–69 Constraints, 57
Buildah, 161, 162 Containerization, 39, 53, 99, 100,
building images, 164, 165 104, 110, 111, 115, 122, 123,
features, 162, 163 158, 175
Podman and, 163, 164 Containerizing, 6, 11, 32, 96, 97,
Build Once, Run Anywhere 132, 158, 209, 232
(BORA), 17, 18, 36 Containers, 1, 22–24, 27, 35, 40, 65,
104, 110, 117, 139, 147,
149, 162
C agility, 7
Centralized repositories, 54 analogy, 5, 6
Clair, 92, 226 black boxes, 4
Clean up containers, 58 cloud platforms, 8
Client-server architecture, 20, 37 communication, 66
234
INDEX
235
INDEX
236
INDEX
237
INDEX
L N
Latest tags, 53 Native Image
Layer caching, 43, 218 benefits, 175
Lego, 1 Docker and, 176
Linux Containers, 2 drawbacks, 175
explanation, 174, 175
Java and, 174
M JIT vs. AOT compiler, 177, 178
Macvlan driver, 70 JVM vs. GraalVM, 178, 179
Maven, 33–35, 96, 99, 110, 130, Networking, 58, 60, 65, 66,
183, 227–229 69–71, 81, 165
Maven image, 221, 228–230 Nginx, 14, 35, 69
Maven Jib plugin, 101, 102
Microservices, 3, 13, 24, 32, 51, 72,
78, 173, 184, 217
O
module-path, 214 Open Container Initiative (OCI),
Mounting, 28, 57, 58, 74, 8, 116, 120, 161–164, 171
75, 168 Orchestration, 2, 8, 12, 19, 148,
Multi-cloud deployments, 13 165, 184
Multi-host communication, 71 Order matters, 43, 44
Multiple base images, 44 Out-of-date tags, 51
Multiple languages, 177, 179 Overlay driver, 70
238
INDEX
P Maven/Gradle, 188
project onboarding, 187
Persistence, 65, 72, 73, 76
project setup, 186
Platform-as-a-Service market, 2
streamlined dependencies, 186
Pod concept, 148
user-friendly, 185
Podman, 19
binary downloading, 150
dashboard, 152 R
Desktop, 151
REST API, 20–23
.dmg file, 149–156
Rollbacks, 50
Engine, 156
features, 148
installation, 151 S
machine, 154
Scalability, 10, 55, 63, 104
open-source software, 147
Scaling, 8, 20, 55, 82, 85
setting up, 148, 149
Secrets, 45, 81, 124, 139, 145,
setup, 154
225, 227
Portability, 7, 72, 104, 135, 169,
Security, 11, 69, 108, 117, 147, 148,
195, 208
166, 170, 171, 217
Port mapping, 27, 57, 58, 160
Dockerfile, 92, 93
PostgreSQL, 14, 84
vulnerabilities, 225
Private registries, 53, 54, 226
Security concerns, 45, 166
Self-contained entities, 6
Q Self-containment, 8
Quality assurance, 51 Semantic versioning, 52
Quarkus, 184 Size optimization, 45, 93
code generation, 186 Solomon Hykes, 2, 17
container image, 191 Spotify, 99, 110, 111, 114, 121
deployment flow, 190 Spring Boot, 83–85, 194
downloading, 186 annotation, 204
extension, 186 dependencies setup, 203, 204
features, 184 systems, 202
Java framework, 184 Testcontainers, 203
with Kubernetes, 185, 186 Spring Boot 3, 180, 181
239
INDEX
240