5 Tips for Creating Docker Images with Spring Boot

Written by avinashsingh | Published 2020/06/30
Tech Story Tags: spring-boot | microservices | docker | maven | java | kubernetes | orchestration | microservice-architecture

TLDR Java Developer I am sure you must have heard of spring boot and probably Docker as well. Docker is phenomenon that has taken DevOps world by a storm. In this tutorial I will cover brief description of Docker containers and the essential tips that will help you get most out of Docker with Spring Boot. The most important one is using the layered design of docker to avoid large size of Spring Boot apps. We will look at that in tips section of this tutorial. The first step is to create a demo hello world application using spring boot app.via the TL;DR App

Keep up with the latest and best practices to build spring boot docker images.
As a Java Developer I am sure you must have heard of spring boot and probably Docker as well. Docker is phenomenon that has taken DevOps world by a storm.
Every new project and old one’s too are migrating to containers.
In this tutorial I will cover brief description of Docker containers and the essential tips that will help you get most out of Docker with Spring Boot.
Source code of the demo project is at

So what are these containers ?

Many view containers as virtual machines. They’re not. Well, kind of not.
A container is a virtual walled environment for your application. It’s literally a ‘container’ inside the host OS.
Thus your application works like it is in its own self contained environment, but it’s actually sharing operating system resources of the host computer. Because of this, containers are more resource efficient than full-blown virtual machines.
Clearly, this is more efficient computing than running a full blown Virtual Machine.
Docker is an open-source project for automating the deployment of applications as portable, self-sufficient containers that can run on the cloud or on-premises.
Because containers require far fewer resources (for example, they don’t need a full OS), they’re easy to deploy and they start fast. This allows you to have higher density, meaning that it allows you to run more services on the same hardware unit, thereby reducing costs.
This efficient mechanism is used by most cloud providers to save them of resources.
Google, Amazon and Microsoft deploy millions of containers every day.
Let’s begin this tutorial of deploying spring boot app on docker and 5 awesome tips for your docker build,
Step 1: Create a Hello World App Spring Boot application
Our first step is to create a demo hello world application.
I will be using below git repository for the spring boot app.
If you are interested in learning about creating a beginner’s spring boot app have a look at this article.
Step 2 : Adding Dockerfile
If you have downloaded github repo above, you would notice that we are using 
Spring Boot 2.3.0
Spring Boot 2.3.0
comes with several changes in it’s support for docker.
The most important one is using the layered design of docker to avoid large size of spring boot apps. We will look at that in tips section of this tutorial.
Let’s add a sample
Dockerfile
,
FROM openjdk:15-jdk-slim
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar

ENTRYPOINT ["java","-jar","/app.jar"]
Run mvn package from your IDE or terminal to generate the spring boot fat jar.
Make sure you have spring boot maven plugin added to generate the fat jar.
<build>
	  <plugins>
	    <plugin>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-maven-plugin</artifactId>
	    </plugin>
	</plugins>
</build>
Now let’s run below docker command to build the image,
docker build -t learnings/spring-boot-docker-demo 
This will create an image in Docker registry,
Let’s run this Docker image as a container,
docker run -p 3050:3050 --name spring-boot-docker-demo -t learnings/spring-boot-docker-demo
Docker layers allows us to keep our build images small in size as we make changes to our application.
If our application code is changed then only application/application layer is affected and other layers stay cached.
You can verify it by making changes to the RestController and running the docker build.
docker build -t learnings/spring-boot-docker-demo .
Check the history of your image to notice that the newly created images are in KB’s,
docker history learnings/docker-spring-boot-demo
Tip 3 # : Build docker image from Maven Wrapper
Our docker build seems to be working but it is missing one key part,
We are creating our fat jar files manually and then going to terminal to
run docker build
This will not work with your CI/CD pipelines as you would need to do your maven build in your CI/CD tool.
We can combine these two steps in either direction,
We can use a maven plugin to generate the docker image during maven build
Or,
We can use maven wrapper to call maven build from inside our Dockerfile.
I prefer using maven wrapper so I will use that here,
Make sure you have Maven before below steps, go to your project directory and run 
mvn -N io.takari:maven:wrapper
This will install the maven wrapper in your project. Maven 3.7 plans to integrate this to Maven by default.
Add maven wrapper instructions to Dockerfile and build again,
FROM openjdk:15-jdk-slim as bulid
WORKDIR application

COPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
COPY src src

RUN ./mvnw  install -DskipTests

RUN cp /application/target/*.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract

FROM openjdk:15-jdk-slim
WORKDIR application
COPY --from=bulid application/dependencies/ ./
COPY --from=bulid application/spring-boot-loader/ ./
COPY --from=bulid application/snapshot-dependencies/ ./
COPY --from=bulid application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
No need to run maven build anymore before bjuilding your docker images.
Tip 4 # : Use a separete docker user
Docker runs it’s command in container as a root user,
Just as in classic VM-deployments, processes should not be run with root permissions. Instead the image should contain a non-root user that runs the app.
In a Dockerfile, this can be achieved by adding another layer that adds a (system) user and group, then set it as the current user (instead of the default, root):
RUN addgroup demogroup && adduser  --ingroup demogroup --disabled-password demo
USER demo
Tip 5 # : Caching build process using Docker build cache
We have added maven build process to our Dockerfile but there is one performance concern. Each time we run 
docker bulid
 , it runs and downloads the maven packages as it creates a new container every time.
Docker has an experimental feature to avoid this,
You can add below as your first line in Dockerfile to enable experimental features.
# syntax=docker/dockerfile:experimental
We need to also add below to specify that we want to cache the mount before we run maven build
RUN --mount=type=cache,target=/root/.m2 ./mvnw install -DskipTests
Our final Dockerfile loos like below,
# syntax = docker/dockerfile:experimental

FROM openjdk:15-jdk-slim as bulid

RUN addgroup demogroup &amp;&amp; adduser  --ingroup demogroup --disabled-password demo
USER demo

WORKDIR application

COPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
COPY src src

RUN --mount=type=cache,target=/root/.m2 ./mvnw  install -DskipTests

RUN cp /application/target/*.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract

FROM openjdk:15-jdk-slim
WORKDIR application
COPY --from=bulid application/dependencies/ ./
COPY --from=bulid application/spring-boot-loader/ ./
COPY --from=bulid application/snapshot-dependencies/ ./
COPY --from=bulid application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
If you run it you will probably see the error,
Error response from daemon: Dockerfile parse error line 13: Unknown flag: mount
We need to run the docker build with a special command to enable experimental features,
DOCKER_BUILDKIT=1 docker build -t learnings/spring-boot-docker-demo .
First time I ran this, my maven build step took almost a minute,
=> [bulid  7/10] COPY src src                                                                                                                       0.2s
 => [bulid  8/10] RUN --mount=type=cache,target=/root/.m2 ./mvnw  install -DskipTests                **57.3s**
 => [bulid  9/10] RUN cp /application/target/*.jar app.jar
On the next run I could see the builds running almost instantaneously,
=> CACHED [bulid  8/10] RUN --mount=type=cache,target=/root/.m2 ./mvnw  install -DskipTests                       0.0s
 => CACHED [bulid  9/10] RUN cp /application/target/*.jar app.jar                                                  0.0s
 => CACHED [bulid 10/10] RUN java -Djarmode=layertools -jar app.jar extract                                        0.0s
                     0.0s
We have successfully reduced build time in our docker images.
That’s all folks, as you can see we have implemented a spring boot app from scratch and learned the tips that will help ypu take your build with Docker to next level.
If you have any questions or feedback don’t hesitate to write your thoughts in the comments/responses section.
For issues related to code, feel free please create an issue directly in GitHub repository.
Thank you for reading! If you enjoyed it, please share/comment for it.

Published by HackerNoon on 2020/06/30