Dockerizing your Spring Boot applications

In this article, you’ll learn how to build a docker image for running a spring boot application. I’ll first give you a brief idea of docker, then we’ll create a docker image for our spring boot application, and run it locally. Finally, we’ll push the docker image to docker hub.

So let’s get started!

A Quick introduction to Docker

Docker is a software platform that enables developers to develop, ship, and run applications anywhere with the help of containers.

Now what the heck is a container and what problem does docker solve with containers?

Well, Let’s understand that by asking a few questions. Does any of the following sound familiar to you?

  • “It runs on my machine!!”

  • “I think your Tomcat version is outdated!

  • “I don’t want to install 10 different libraries and tools before being able to run your application. Can’t it come in a packaged form with all the libraries and tools it needs?”

  • “We have applications written in different languages, tools, system libraries, and environments. Is there a way we can run them independently on the same infrastructure?”

I bet some of them does.

Docker solves these problems by creating a lightweight, standalone, executable package of your application that includes everything needed to run it including the code, the runtime, the libraries, tools, environments, and configurations.

These standalone executable packages are called docker images. And a running instance of a docker image is called a docker container.

Now, these container images can be shared, shipped and run anywhere in any environment. They will behave exactly the same regardless of the environment they run in.

Spring Boot Docker Illustration

Moreover, you can run multiple containers of completely different configurations on the same infrastructure. All containers are completely isolated and run independently from each other.

Cool, isn’t it? Well, let’s now learn how to run a spring boot application inside a docker container. But before that, go ahead and install docker community edition on your platform.

Also Read: What is a Container and What is the difference between Containers and Virtual Machines

Spring Boot with Docker: Dockerizing a Spring Boot application

1. Downloading the application to Dockerize

In this article, we’ll dockerize a web socket based group chat application built with spring boot. You can download the application from github -

$ git clone https://github.com/callicoder/spring-boot-websocket-chat-demo

There is a live demo of this application hosted on Heroku. Go check that out if you’re curious. You can also read the tutorial to learn how to build it from scratch.

All right! Let’s now learn how to create a docker image of this spring boot application.

2. Defining a docker image with Dockerfile

Go to the application’s root directory and create a new file named Dockerfile.

$ cd spring-boot-websocket-chat-demo
$ touch Dockerfile

Dockerfile is where we define the docker image and specify all the configurations required to run the app. Following is the Dockerfile for our spring boot application -

# Start with a base image containing Java runtime
FROM openjdk:11

# Add Maintainer Info
LABEL maintainer="callicoder@gmail.com"

# Add a volume pointing to /tmp
VOLUME /tmp

# Make port 8080 available to the world outside this container
EXPOSE 8080

# The application's jar file
ARG JAR_FILE=target/websocket-demo-0.0.1-SNAPSHOT.jar

# Add the application's jar to the container
ADD ${JAR_FILE} websocket-demo.jar

# Run the jar file 
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/websocket-demo.jar"]

The Dockerfile is very simple and declarative. Let’s go through each line of the Dockerfile and understand the details.

  • FROM: A docker image can use another image available in the docker registry as its base or parent image. In the above example, we use the openjdk:11 image as our base image.

  • LABEL: The LABEL instruction is used to add metadata to the image. In the above Dockerfile, we have added some info about the maintainer of the image through LABEL instruction.

  • VOLUME: Volumes are a mechanism to persist data generated by the container on the Host OS, and share directories from the Host OS with the container.

    The VOLUME instruction creates a mount point on the container with the specified path. When you run the container, you can specify the directory on the Hot OS to which the given mount point will be mapped to. After that, anything that the container writes to the mounted path is written to the mapped directory on the Host OS.

    One of the most common use cases of volumes is to store the log files generated by the container on the Host OS. For example, Let’s say that your application writes log files to a location /var/log/app.log.

    You can mount a VOLUME with path /var/log in the Dockerfile, and then specify the directory on the Host OS to which this mount point will be mapped to while running the container. After that, you’ll be able to access the logs from the mapped directory on the Host OS.

    In the above Dockerfile, we created a mount point with path /tmp because this is where the spring boot application creates working directories for Tomcat by default. Although it’s not required for this spring boot application because who cares about tomcat directories. But if you want to store stuff like tomcat access logs, then VOLUMES are very useful.

    You can learn more about Volumes from the official documentation.

  • EXPOSE: As the name suggests, this instruction allows you to expose a certain port to the outside world.

  • ARG: The ARG instruction defines a variable with a default value. You can override the default value of the variable by passing it at build time.

    ARG <name>[=<default value>]

    Once defined, the variable can be used by the instructions following it.

  • ADD: The ADD instruction is used to copy new files and directories to the docker image.

  • ENTRYPOINT: This is where you configure how the application is executed inside the container.

3. Building the Docker image

Now that we have defined the Dockerfile, let’s build a docker image for our application.

Before building the docker image, you need to make sure that you’ve packaged the application in the form of a jar file using maven. You can type the following command from the root directory of the project to package it -

$ mvn clean package

The above command creates a jar file in the target directory of the project.

Let’s now build the docker image by typing the following command -

$ docker build -t spring-boot-websocket-chat-demo .

That’s it. You can now see the list of all the docker images on your system using the following command -

$ docker image ls

REPOSITORY                        TAG                 IMAGE ID            CREATED             SIZE
spring-boot-websocket-chat-demo   latest              30ad8958ac67        22 hours ago        126MB
openjdk                           8-jdk-alpine        224765a6bdbe        3 months ago        102MB

This should display our newly built docker image.

4. Running the docker image

Once you have a docker image, you can run it using docker run command like so -

$ docker run -p 5000:8080 spring-boot-websocket-chat-demo

In the run command, we have specified that the port 8080 on the container should be mapped to the port 5000 on the Host OS.

Once the application is started, you should be able to access it at http://localhost:5000.

The container runs in the foreground, and pressing CTRL + C will stop it. Let’s now see how to run the container in the background.

Running the docker image in the background, in detached mode.

You can use the -d option in docker run command to run the container in the background -

$ docker run -d -p 5000:8080 spring-boot-websocket-chat-demo
1c3528715862a8a8efb712c85bc8ab61f3419c04eb6dc613af76c89846d316e0

The above command starts the container in the background and gives you the container ID. You can see the list of all containers running in your system using the following command -

$ docker container ls

CONTAINER ID        IMAGE                             COMMAND                  CREATED              STATUS              PORTS                            NAMES
1c3528715862        spring-boot-websocket-chat-demo   "java -Djava.securit…"   About a minute ago   Up About a minute   8080/tcp, 0.0.0.0:4000->80/tcp   vigorous_stallman

5. Pushing the docker image to docker hub

Now let’s push the docker image to docker hub so that other people can download and consume our image.

  • Login with your Docker Id

    $ docker login
    Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
    Username (callicoder): callicoder
    Password:
    Login Succeeded
  • Tag the image

    To push a local image to docker registry, you need to associate the local image with a repository on the docker registry. The notation for the repository on docker registry is `username/repository:tag`.
    
    To tag an image, we use the `docker tag` command -
    
    ```bash
    $ docker tag image username/repository:tag
    ```
    
    For example, Here is how we can tag the local image of our spring boot application -
    
    ```bash
    $ docker tag spring-boot-websocket-chat-demo callicoder/spring-boot-websocket-chat-demo:0.0.1-SNAPSHOT
    ```
    
    Make sure to replace my username `callicoder` with your docker id in the above command. 
    
    Once the tagging is done, you can type `docker image ls` in the terminal to see the newly tagged image -
    
    ```bash
    $ docker image ls
    
    REPOSITORY                                   TAG                 IMAGE ID            CREATED             SIZE

    callicoder/spring-boot-websocket-chat-demo 0.0.1-SNAPSHOT 30ad8958ac67 23 hours ago 126MB spring-boot-websocket-chat-demo latest 30ad8958ac67 23 hours ago 126MB openjdk 8-jdk-alpine 224765a6bdbe 3 months ago 102MB

    ```
  • Push the image to docker hub

    Finally, use the docker push command to push the tagged image to docker hub like so -

    $ docker push callicoder/spring-boot-websocket-chat-demo:0.0.1-SNAPSHOT

    Again, don’t forget to replace the username callicoder with your docker id.

    And That’s all! The image is now published on the docker hub at the following link - https://hub.docker.com/r/callicoder/spring-boot-websocket-chat-demo/

6. Pulling the image from docker hub and running it

After we publish the image to docker hub, anyone can pull this image and run it in their environment. Type the following command to pull and run the image on your machine that we just pushed to docker hub -

$ docker run -p 5000:8080 callicoder/spring-boot-websocket-chat-demo:0.0.1-SNAPSHOT

The docker run command pulls the image from docker hub if it is not available locally, and then runs it.

You see how easy it is to share your image with others. People don’t need to install anything whatsoever to run your application. They just need to pull the image and run it with docker.

Automating the Docker image creation and publishing using dockerfile-maven-plugin

You can automate everything from building the docker image to publishing it on docker hub using dockerfile-maven-plugin.

Add the plugin to the pom.xml file with the following configurations -

<plugin>
	<groupId>com.spotify</groupId>
	<artifactId>dockerfile-maven-plugin</artifactId>
	<version>1.4.0</version>
	<configuration>
		<!-- replace `callicoder` with your docker id-->
		<repository>callicoder/spring-boot-websocket-chat-demo</repository>
		<tag>${project.version}</tag>
		<buildArgs>
			<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
		</buildArgs>
	</configuration>
	<executions>
		<execution>
			<id>default</id>
			<phase>install</phase>
			<goals>
				<goal>build</goal>
				<goal>push</goal>
			</goals>
		</execution>
	</executions>
</plugin>

The plugin’s configuration includes the remote repository name and the repository tag. Please replace the username callicoder with your docker Id in the <repository> element.

We’re also passing the JAR_FILE argument inside <buildArgs> element. Remember we had added ARG JAR_FILE instruction to the Dockerfile? The argument passed here will override that value.

Here is how you can build the docker image using docker-file-maven plugin -

$ mvn package dockerfile:build

The above command first packages the application in the form of a jar file, and then builds the docker image.

Finally, you can push the docker image to the docker registry using dockerfile:push command -

$ mvn dockerfile:push

Now to automate it further, we have registered the dockerfile:build and dockerfile:push goals to the install phase of maven build life cycle using the <executions> tag.

So whenever you run mvn install, the build and push goals of dockerfile-maven-plugin are executed, and your docker image is built and pushed to docker hub.

The dockerfile-maven-plugin uses the authentication information stored in any configuration files ~/.dockercfg or ~/.docker/config.json to push the docker image to your docker profile. These configuration files are created when you login to docker via docker login.

You can also specify authentication details in maven settings.xml or pom.xml files. Check out the official Readme of the plugin for more information on that.

Conclusion

The application that we dockerized in this article is very simple. It doesn’t use any database or communicate with other services. In the next article, you’ll learn how to dockerize a complex service with a database using docker compose.

As always, Thanks for reading. Have a good day.