Post

Tutorial

Containerise Spring boot application with Docker


Containerise Spring boot application with Docker

Docker is a containerization tool that helps speed up the development and deployment processes. If you’re working with microservices, Docker makes it much easier to link together small, independent services. It also helps to eliminate environment-specific bugs since you can replicate your production environment locally.

We would discuss about below points in this tutorial:

  • What is Docker ?
  • Creating Image with Docker file
  • Creating Image with Maven plugin

What is Docker?

Docker is a popular tool to make it easier to build, deploy and run applications using containers. Containers allow us to package all the things that our application needs like such as libraries and other dependencies and ship it all as a single package. In this way, our application can be run on any machine and have the same behaviour.

A Docker container, unlike a virtual machine, does not require or include a separate operating system. Instead, it relies on the kernel’s functionality and uses resource isolation for CPU and memory, and separate namespaces to isolate the application’s view of the operating system.

Creating Image with Docker file

A dockerfile is a text file that contains the necessary commands to assemble an image. Once a Dockerfile is created, the administrator uses the docker build command to create an image based on the commands within the file. The commands and information within the dockerfile can be configured to use specific software versions and dependencies to ensure consistent and stable deployments.

A Dockerfile uses the following commands for building the images:

  • ADD: Copy files from a source on the host to the container’s own filesystem at the set destination.
  • CMD: Execute a specific command within the container. ENTRYPOINT: Set a default application to be used every time a container is created with the image.
  • ENV: Set environment variables.
  • EXPOSE: Expose a specific port to enable networking between the container and the outside world.
  • FROM: Define the base image used to start the build process.
  • MAINTAINER: Define the full name and email address of the image creator.
  • RUN: Central executing directive for Dockerfiles.
  • USER: Set the UID (the username) that will run the container.
  • VOLUME: Enable access from the container to a directory on the host machine.
  • WORKDIR: Set the path where the command, defined with CMD, is to be executed.

Don’t worry, you don’t need to know each and every command mentioned above to work with docker. You can start with most important commands and build up from that. You will create a working Dockerfile example in the following section.

Creating Dockerfile

We would use a sample Spring Boot project to demonstrate Docker containerisation.

You can clone the below repository and try along with tutorial

bhanuchaddha/quotes

This service return a random quote every time the mentioned url is called.

Check out the tag v1.0 to start with. You can test the application as specified in README.md file.

When you run the url http://localhost:8080/random-quote , you will get response like below from the service

{
   "result": "The right way is not always the popular and easy way. Standing for right when it is unpopular is a true test of moral character. ",
   "setOrExpired": "true"
}

Now when you are ready with the working code, let’s begin the docker journey.

We need to create the artifact before we create the Docker Image. Run below command from the root directory of the project

You should see that artifact have been created in the target directory, similar to below image.

Artifact has been created

Now let’s create a file in the root directory of your project named DockerFile . Paste the below lines of code into DockerFile.

FROM java:8-jdk-alpine 
COPY ./target/quotes-0.0.1-SNAPSHOT.jar /usr/application/ 
WORKDIR /usr/application 
ENTRYPOINT [“java”,”-jar”,”quotes-0.0.1-SNAPSHOT.jar”]

A Dockerfile must start with a ‘FROM’ instruction. We use this keyword to instruct Docker to use the Base image from which we are building. This is followed by 1 or more arguments to declare the arguments that are used in the Dockerfile. We can create docker image from scratch or use existing images available in docker-hub. In our example, we use the image ‘java’ with tag ‘8-jdk-alpine’. This image is Java-enabled alpine Linux image and the main advantage of using alpine based image is to enhance security of the container and to reduce the container size. The version of the JDK is 8.

  • COPY — This instructs Docker to copy new filer or directories from and copies them to the filesystem of the container at the path . In our example, we copy .jar file to build the Linux image inside /usr/application.
  • WORKDIR — This instructs the Docker to set the working directory for RUN, CMD, ENTRYPOINT, COPY, ADD instructions specified in the Dockerfile. In our example, we had set the workdir to /usr/application. Hence, we need not have to write the full path again and again.
  • ENTRYPOINT — This instructs Docker to configure a container that will run as an exe file, by specifying how to run the application. In our example, we run our spring-boot-app as a java –jar <app-name>.jar.

Build Image

As instructions to create Docker Image has been specified. Let’s build the docker image now. Go to the root of the project and run below command.

docker build -t quotes .

-t is used to give a tag to the image. We can refer to the image by this tag later on. Otherwise we can refer to the image by unique IMAGE ID .

You can verify that image is created by running docker images

docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
quotes              latest              d22119a3a9d4        15 seconds ago      164MB

We know that each container is an isolated environment in itself thus we can not access the service running in the docker container directly. We need to forward the port from the docker container to the port in host operating system. we specify this with -p 8080:8080 argument to the docker run command.

Testing

Our Quotes service is dockerized now. You can access the service from the same url like below and you should see the random quote from the service.

http://localhost:8080/random-quote
{
   "result": "It is better to understand a little than to misunderstand a lot. ",
   "setOrExpired": "true"
}

Dockerizing using Maven

In the previous section we wrote a simple Dockerfile and build 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.

To avoid above issues we might want to create docker image dynamically. We can use our old friend Maven for that. 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.

Complete code with maven plugin changes can be found at https://github.com/bhanuchaddha/quotes

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>
        <id>docker-build</id>
        <activation>
            <property>
                <name>docker-build</name>
            </property>
        </activation>
        <build>
            <plugins>
                <plugin>
                    <groupId>io.fabric8</groupId>
                    <artifactId>docker-maven-plugin</artifactId>
                    <version>0.30.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>

Please refer the repository for the complete POM file.

After you have added the docker plugin into the POM file , go to the root of the project and type below command. This command will create docker file with correct version for you.

mvn clean install -Ddocker-build

You should see something like below

[INFO] --- docker-maven-plugin:0.30.0:build (build) @ quotes ---
[INFO] Copying files to /Users/bhanuchaddha/Documents/project/quotes/target/docker/quotes/build/maven
[INFO] Building tar: /Users/bhanuchaddha/Documents/project/quotes/target/docker/quotes/tmp/docker-build.tar
[INFO] DOCKER> [quotes:latest]: Created docker-build.tar in 1 second 
[INFO] DOCKER> Step 1/3 : FROM java:8-jdk-alpine
[INFO] DOCKER> 
[INFO] DOCKER> ---> 3fd9dd82815c
[INFO] DOCKER> Step 2/3 : COPY maven /maven/
[INFO] DOCKER> 
[INFO] DOCKER> ---> 5bfac3e72519
[INFO] DOCKER> Step 3/3 : ENTRYPOINT ["java","-jar","/maven/quotes-0.0.1-SNAPSHOT.jar"]
[INFO] DOCKER> 
[INFO] DOCKER> ---> Running in 2f9a72dbdea4
[INFO] DOCKER> Removing intermediate container 2f9a72dbdea4
[INFO] DOCKER> ---> 877443bfc07b
[INFO] DOCKER> Successfully built 877443bfc07b
[INFO] DOCKER> Successfully tagged quotes:latest
[INFO] DOCKER> [quotes:latest]: Built image sha256:87744
[INFO] DOCKER> [quotes:latest]: Removed old image sha256:d2211

Your docker image is ready and this can be run using below mentioned command.

docker run -p 8080:8080 quotes

Inspiration

Please do let me know if you have any doubts or if you need any topic to be covered in more depth.

comments powered by Disqus