Overview
In this article, we'll cover the process of creating a Docker image of a Spring Boot application, using Dockerfile and Maven and then run the image we've created.
The source code for this tutorial can be found on GitHub.
This tutorial assumes that you have Docker installed on your machine. If not, you can follow the official Docker install guide based on your operating system.
If you'd like to read more about Docker, we've covered it in detail in - Docker: A High Level Introduction.
The Spring Boot Application
Let's start off with a simple Spring Boot Application. The best way to start with a skeleton project is to visit Spring Initializr. Select your preferred version of Spring Boot, and add the "Web" dependency. Generate it as a Maven project and you're all set!
The project includes a simple REST controller with a single mapping that just expects a name as the path variable and then generates a string to greet it:
@RestController
public class DemoController {
@GetMapping("/greet/{name}")
public String greeting(@PathVariable String name) {
return "Hi!! " + name;
}
}
To run the application, use the following Maven command from the project root folder:
$ mvn spring-boot:run
As usual, the application will be running on port 8080. To test this endpoint, navigate your browser (or use curl, postman, etc.) to "http://localhost:8080/greet/john", you will see a response that looks something like:
Dockerizing the Spring Boot App
Now let's dockerize our previously made Spring Boot Application. We will cover the two most commonly used approaches:
- Dockerfile – Specifying a file that contains native Docker commands to build the image
- Maven – Using a Maven plugin to build the image
Dockerizing using Dockerfile
A Dockerfile is just a regular .txt
file that includes native Docker commands that are used to specify the layers of an image. To do so, let's create a text file named "Dockerfile":
The content of the file itself can look something like this:
FROM java:8-jdk-alpine
COPY ./target/demo-docker-0.0.1-SNAPSHOT.jar /usr/app/
WORKDIR /usr/app
RUN sh -c 'touch demo-docker-0.0.1-SNAPSHOT.jar'
ENTRYPOINT ["java","-jar","demo-docker-0.0.1-SNAPSHOT.jar"]
Let's take a look at the commands and fully understand them before proceeding:
- FROM – The keyword
FROM
tells Docker to use a given base image as a build base. We have used 'java' with the tag '8-jdk-alpine'. Think of a tag as a version. The base image changes from project to project. You can search for images on docker-hub. - COPY - This tells Docker to copy files from the local file-system to a specific folder inside the build image. Here, we copy our
.jar
file to the build image (Linux image) inside/usr/app
. - WORKDIR - The
WORKDIR
instruction sets the working directory for anyRUN
,CMD
,ENTRYPOINT
,COPY
andADD
instructions that follow in the Dockerfile. Here we switched the workdir to/usr/app
so we don't have to write the long path again and again. - RUN - This tells Docker to execute a shell command-line within the target system. Here we practically just "touch" our file so that it has its modification time updated (Docker creates all container files in an "unmodified" state by default).
- ENTRYPOINT - This allows you to configure a container that will run as an executable. It's where you tell Docker how to run your application. We know we run our spring-boot app as
java -jar <app-name>.jar
, so we put it in an array.
More documentation can be found on the Dockerfile reference page.
Before moving further, we need a Spring Boot .jar
file. This file will be used to create the Docker image as mentioned above.
Run the mvn clean install
command to make sure that it's generated.
Let's build the image using this Dockerfile. To do so, move to the root directory of the application and run this command:
$ docker build -t greeting-app .
We built the image using docker build
. We gave it a name with the -t
flag and specified the current directory where the Dockerfile is. The image is built and stored in our local docker registry.
Let's check our image:
$ docker images
And finally, let's run our image:
$ docker run -p 8090:8080 greeting-app
We can run Docker images using the docker run
command.
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
We know that each container is an isolated environment in itself and we have to map the port of the host operating system - 8090
and the port inside the container - 8080
, which is specified as the -p 8090:8080
argument.
Now, we can access the endpoint on http://localhost:8080/greet/john
:
Our Spring Boot application is successfully running within a Docker container!
Dockerizing using Maven
In the previous section we wrote a simple Dockerfile and built our application using the native docker build
command. However, there are a couple of issues that we may encounter in our projects using this method:
- The
.jar
name - We have to mention the jar name (along with the version) in the file. As our application grows our versions will change and we have to, again and again, update this Dockerfile too. - Using the terminal - We have to manually open a terminal and run Docker commands. It would be nice if we could make it a part of a Maven life-cycle so that we can build images as a part of our CI/CD (Continuous Integration/Continuous Delivery) pipelines.
There are many Maven plugins available that we can use in our pom.xml
file that would make our life much easier. The way that this Maven plugin works is that it internally creates the Dockerfile based on the configuration in the pom.xml
file and then uses the generated Dockerfile to build the image.
Using this method, there's no need for us to manually update the name, nor run the terminal.
We will be using the fabric8io/docker-maven-plugin.
The plugin should be located in our pom.xml
file after the build
tag. This will be an optional build plugin using Maven profiles. It's always a good idea to use this via profiles because we want the regular mvn clean install
command to work on a developer's machine, which doesn't have Docker installed too:
<profiles>
<profile>
<activation>
<property>
<name>docker</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.26.0</version>
<extensions>true</extensions>
<configuration>
<verbose>true</verbose>
<images>
<image>
<name>${project.artifactId}</name>
<build>
<from>java:8-jdk-alpine</from>
<entryPoint>
<exec>
<args>java</args>
<args>-jar</args>
<args>/maven/${project.artifactId}-${project.version}.jar</args>
</exec>
</entryPoint>
<assembly>
<descriptorRef>artifact</descriptorRef>
</assembly>
</build>
</image>
</images>
</configuration>
<executions>
<execution>
<id>build</id>
<phase>post-integration-test</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Let's take a closer look at this:
- Our profile is named
docker
- If we have to build the image using Maven, we should run the command with-Ddocker
. - The
<name>
tag - This tag specifies the image name, which is theartifactId
- in our case, it'sdemo-docker
. - The
<from>
tag - This tag specifies the base image ofjava:8-jdk-alpine
. - The
<args>
tag - This tag is used to specify how the image should run.
Now let's build the image:
$ mvn clean install -Ddocker
Let's check our image:
$ docker images
Finally, we'll run our image:
$ docker run -p 8090:8080 demo-docker
Now check the REST endpoint (http://localhost:8090/greet/john) in the browser.
Note: You have to stop the previously running containers that are using port 8090
, or else you will get an error.
You can easily check what containers are running using:
$ docker ps
Now, if the port is already in use, you can either change the port or remove the old container using:
$ docker rm -f <container-id>
Another easy way is to stop all of the containers:
$ docker rm -f $(docker ps -a -q)
Conclusion
In this article, we covered the two most commonly used ways to containerize a Spring Boot application using Docker.
The first approach was done by using a simple Dockerfile to build the image, and the second approach is using a Maven plugin.
Once the image is created, there are many ways to run it. In this article, we relied on the native docker run
command. A more approachable way is to use docker-compose if you have many images that you need to run.
For a production environment we usually go for Kubernetes or Docker-swarm so as to scale our application based on network traffic automatically.