Sailing Smoothly with Docker: How to dockerize a Spring Boot Application.

Sailing Smoothly with Docker: How to dockerize a Spring Boot Application.

·

5 min read

In the previous article, we explored the magic of Docker and its potential to revolutionize application deployment. Now, let's put this knowledge into practice by containerizing a Spring Boot application that interacts with a MySQL database. Buckle up, as we leverage Docker and Docker Compose to create a portable, scalable, and efficient development environment!

In this article, we have used Spring Boot and MYSQL, but we can use the same techinque to dockerize apps based on other tech stacks.

Understanding the Spring Boot's Application Structure:

Please find the link of the Spring Boot's application, which we are going to containerize in this article

Spring Boot Application Github's Link

Our Spring Boot's application is divided broadly in three layers:

  1. Controller: spring boot controllers act as the entry point for HTTP requests, routing them, binding data, orchestrating business logic, preparing responses, and interacting with clients

  2. Service: perform business logic

  3. Repository: acts as the abstraction layer for interacting with database

The aforementioned, spring boot application contains following API's

  1. saveEmployee

  2. getAllEmployees

  3. getEmployeeById

  4. updateEmployee

  5. deleteEmployee

#curl command to save the employee to the database 
curl -X POST \
  http://localhost:8080/api/employees \
  -H 'Content-Type: application/json' \
  -d '{
    "firstName": "John",
    "lastName": "Doe",
    "email": "john.doe@example.com"
  }'

An employee record with email will be saved to the database.

#curl command to get all employees
curl -X GET \ 
  http://localhost:8080/api/employees \          
  -H 'Content-Type: application/json'

You will get all employees which are persisted in the employees table

Let's Containerize:

To containerize our application, we'll create a Dockerfile. This acts as a blueprint, defining the environment and configuration needed to run the application within a Docker image. Let's start building our Dockerfile.

#FROM: Specifies the base image to build upon. In this case, it's maven:3.8.4-openjdk-17, which is an image containing Maven and OpenJDK 17.
#AS builder: Assigns a name (builder) to this stage. This allows for multi-stage builds, where you can have different stages for building and running your application.
FROM maven:3.8.4-openjdk-17 AS builder 

#WORKDIR: Sets the working directory inside the container to /app. This is where subsequent commands will be executed.
WORKDIR /app

#Copies files from the Docker build context (the directory containing the Dockerfile) into the container's filesystem. The first . represents the source directory in the build context (current directory), and the second . represents the destination directory in the container (current working directory, which is /app).
COPY . .

#RUN: Executes a command inside the container during the image build process. Here, it runs Maven to build the application. The options -B (batch mode) and -DskipTests (skips running tests) are Maven options. The clean package command cleans any previous build artifacts and packages the application into a JAR file.
RUN mvn -B -DskipTests clean package

#Starts a new stage in the Dockerfile, using another base image (openjdk:17), which contains only the Java runtime environment (JRE) without Maven.
FROM openjdk:17

#Creates a mount point at /tmp in the container. Volumes are used to persist data outside the container's lifecycle. This can be useful for storing logs, configuration files, or any other data that needs to be preserved even if the container is deleted.
VOLUME /tmp

#Informs Docker that the container will listen on port 8080 at runtime. However, this does not actually publish the port; it is merely a documentation for users of the image.
EXPOSE 8080

#Sets the working directory inside the container to /app again, just like in the previous stage.
WORKDIR /app

#Copies the JAR file built in the previous stage (builder) into the current stage. --from=builder specifies the stage to copy from, and /app/target/*.jar is the source path of the JAR file. app.jar is the destination path inside the current stage.
COPY --from=builder /app/target/*.jar app.jar

#Specifies the command that will be executed when the container starts. Here, it runs the Java application by executing the JAR file (app.jar) using the java -jar command.
ENTRYPOINT ["java","-jar","app.jar"]

With our Dockerfile ready, let's build the image for our application.

docker build -t <Image_Name>

Running our application directly from the image wouldn't be ideal, as it depends on MySQL. To manage these interconnected services, we'll leverage Docker Compose.

Docker Compose is a tool that simplifies running multi-container applications. It orchestrates the creation and management of linked containers, ensuring they share a network and can communicate seamlessly. Let's create a docker-compose.yml file to define this configuration.

services:
  api_service:
    restart: always
    # Specifies that the api_service depends on the mysqldb service. This ensures that Docker Compose will start the mysqldb service before starting the api_service.
    depends_on:
      - mysqldb
    # Indicates that the Dockerfile for building the api_service image is located in the current directory (.). This means Docker Compose will build the image using the Dockerfile in the current directory.
    build: .
    ports:
      - "8080:8080"
    # Environment Variables which are needed by our Spring Boot Application
    environment:
      spring.datasource.url: 'jdbc:mysql://mysqldb:3306/ems?allowPublicKeyRetrieval=true&useSSL=false'
      spring.datasource.username: root
      spring.datasource.password: root
  mysqldb:
    image: "mysql:latest"
    ports:
      - 3306:3306
    environment:
      MYSQL_DATABASE: ems
      MYSQL_ROOT_PASSWORD: root

You can bring your multi-container application to life by running the docker-compose up command. This will create and start the services defined in your docker-compose.yml file.

docker-compose up

With Docker Compose managing everything, running docker-compose up will start both the Spring Boot and MySQL containers, establishing the necessary network connection for communication.
Now, let's test our application functionality by attempting to save an employee record.

Now, let's try to fetch all Employees

What's Next:

Our exploration of Docker's layer-based image creation emphasizes the impact each layer has on the final image size. To streamline the build process, consider placing frequently updated sections, such as the "RUN mvn ..." command that builds your application, towards the end of your Dockerfile. This minimizes the number of layers rebuilt during code modifications, leading to faster deployments. By strategically structuring your Dockerfile, you can achieve significant efficiency gains. As part of this optimization, leverage the Multi-Stage File Structure, positioning the least changing elements at the start and frequently changing elements at the end.