Dockerizing a Spring Boot application backed by a MySQL Database

Jan 24, 2024 posted by Ilman Iqbal

This article explains on how a customer-service Spring Boot application that is backed by a MySQL database can be dockerized.

dockerize-spring-boot-mysql-application

Docker is a tool designed to make it easier to create, deploy, and run applications by using containers.

Docker Compose can be used to define and manage multi-container Docker applications. Docker Swarm and Kubernetes are popular for orchestrating multiple containers, scaling them, managing their lifecycle, and ensuring high availability.

With docker, You can create an image of your application, push it to a Docker registry (like Docker Hub), and then pull that image onto any environment where Docker is installed, ensuring your application runs in the same way everywhere.

To create a docker image, you must create a dockerfile. You'd define a Dockerfile, which contains instructions on how to build your Java Spring application's container image. This might involve specifying a base image (like one with Java already installed), copying your application code, installing dependencies, and exposing necessary ports.

An advanced docker file is given below:

# Use a base image with Java pre-installed
FROM openjdk:11-jre-slim

# Set maintainer information
LABEL maintainer="app_creator@example.com"

# set environment variables that can be overridden during container creation if needed. DB_PASSWORD=$DB_USERNAMEchangeit means 'changeit' is appendeded to the existing value of $DB_USERNAME
ENV DB_USERNAME=mysql-service
ENV DB_USERNAME=root
ENV DB_PASSWORD=$DB_USERNAMEchangeit

# create a directory named logs inside the root directory of the container
RUN mkdir -p /logs

# Set the working directory inside the container. This is where subsequent commands will be executed unless they start with root path.
WORKDIR /app

# Copy customer-service spring application JAR to the container as customer-service-app jar
COPY target/customer-service.jar ./customer-service-app.jar

# specify directory within the container for persisting or sharing data, but this data will be lost when the container gets deleted
VOLUME /logs

# expose port 8080 from the Docker container
EXPOSE 8080

# Define the command to run the application. 
# the following line is same as 'CMD java -jar /app/customer-service-app.jar' or CMD ["java", "-jar", "/app/customer-service-app.jar"]
# as WORKDIR is specified above, this command will launch the customer-service-app.jar file within the /app directory.
CMD ["java", "-jar", "./customer-service-app.jar"]
                

CMD vs ENTRYPOINT in dockerfile?

CMD sets default command and/or parameters which can be overridden during container runtime. i.e. When starting a container, if a command is specified along with the docker run command, it overrides the command specified in the CMD instruction. ENTRYPOINT defines the command and parameters that cannot be overridden during container runtime. i.e. When starting a container, if arguments are provided with docker run, they are appended to the command specified by ENTRYPOINT.

COPY vs ADD in dockerfile?

COPY command copies files from the source on the host to a destination in the container. ADD command not only copies files but can also do things like extract compressed archives (e.g., .tar, .tar.gz, .zip) and retrieve files from URLs.

WORKINGDIR in dockerfile?

It defines the default directory for any subsequent RUN, CMD, ENTRYPOINT, COPY, and ADD commands within the Dockerfile. This means you don't need to specify absolute paths for files and directories; commands will execute within this directory unless otherwise specified.

How to build a docker image from a spring boot project?

  1. Define the dockerfile
  2. Execute mvn clean package in project root
  3. Execute docker build -t customer-service[:tag] . in project root. (. specifies the build context, indicating that the Dockerfile is in the current directory)

How to push a docker image to docker hub?

  1. Login to Docker Hub:
    > docker login
    Username: milmaniq
    Password: {enter your password here}
    Login Succeeded
                            
  2. Tag the local docker image with a repository in the docker registry:
    > docker tag customer-service:latest milmaniq/customer-service:1.0
                            
  3. Push the image to docker hub:
    > docker push milmaniq/customer-service:1.0
                             
  4. The docker image will be published in docker hub at the following link:

    https://hub.docker.com/repository/docker/{your-docker-username}/customer-service/general
                            

How to pull an image from docker hub and run it?

  1. Pull the image from docker hub (if it is not locally available) and run it:
    > docker run -p 5000:8000 milmaniq/customer-service:1.0
                            

    You can override the value of DB_USERNAME in Dockerfile or application.properties file when creating a container from this image:

    > docker run -e DB_USERNAME=new_username customer-service:1.0
                                

Other useful commands in docker

# to view the list of docker images in the local system:
> docker image ls

# to delete a docker images in the local system:
> docker image rm postgres:13-beta2-alpine

# to pull a docker image from docker hub:
> docker pull mysql[:tag]

# to find out the ports exposed by a docker image:
> docker inspect mysql:latest
# or to get specific results, you can use the following command
> docker inspect --format='{{.Config.ExposedPorts}}' redis:latest

# to run the docker image in a docker container. (if the docker image is not available loccally, then it will be downloaded automatically from docker hub)
> docker run -d -p 5000:8080 spring-demo-project
# -d -> to run the container in detached mode
# -p -> specifies that the port 8080 on the container must be mapped to port 5000 in the host OS
# you can access the endpoints in the spring-demo project usng base url as http://localhost:5000

# to view the list of all the running containers:
> docker container ls 
# or
> docker ps

# to find out the IP address of the container, (so that this container can communicate with another container:
> docker inspect {container-id}

# to restart a docker container:
> docker restart {container-id}

# to stop a docker container:
> docker stop {container-id}

# to log into the docker container
> docker exec -it {container-id} bash
                    

How to run a MySQL container in windows?

  1. Download docker desktop and install it
  2. Open docker desktop
  3. Pull the latest docker image to your local machine:
    > docker pull mysql
    
    # If you want to download a specific version, then you have to specify the tag as shown in the following command. these tags can be viewed in the tag tab of the selected docker image in the docker hub online
    > docker pull mysql:8.0.33
                             
  4. Create a container from the downloaded docker image:
    > docker run mysql
    # When you run the above command, you will get an error message to set environment variables. So you will have to run the mysql docker container as shown below:
    
    > docker run -d --name mysql8db -e MYSQL_ROOT_PASSWORD=123456 -p 3307:3306 -p 33070:33060 -v mysql8db_data:/var/lib/mysql mysql
    # -e -> is used to set environment variables
    # -d -> is used to run the container in a detached mode
                             

How can you retain the data that is persisted to the container volume?

When you run a container from an image created with this Dockerfile, you can use the --volume or -v flag to get the container volume directory mapped to a:

  1. Named Volume
    > docker run -v mysql_data:/var/lib/mysql
    # The container volume directory will be managed by the Docker's volume system. To manage volumes or view their details, you can use Docker commands like docker volume ls, docker volume inspect, and docker volume rm.
                            
  2. Bind Mount
    > docker run -v C:/Users/Me/local_logs:/logs
    # The local directory local_logs will be mounted to the container's /logs directory
                            

Some usecases when working with a MySQL backed Spring Boot application in Docker

Imagine, you have a customer-service container and a mysql container. Both of these containers are running individually. How to make the customer-service container to connect to the MySQL container?

  1. Ensure that you have the spring.datasource.url of customer-service in the following format:
    spring.datasource.url=jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:1234}/customer_service_db?createDatabaseIfNotExist=true
                            
  2. Create a new docker network:
    > docker network create springboot-mysql-net
                            
  3. Run MySQL container in a network:
    > docker run --name mysqldb --network springboot-mysql-net -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=customer_service_db -p 3309:3306 -p 33090:33060 -d mysql
    # It is important to provide a name for the container
                            
  4. Run the customer-service container in the same network:
    > docker run --network springboot-mysql-net -p 8080:8080 -e DB_HOST=mysqldb -e DB_PORT=3306 customer-service
    # You must pass the host and the container name, and the port as the internal port of the container
                            

Imagine, you want to connect a customer-service container to a local mysql instance that is running on port 3307, (or a mysql docker instance that is exposed from port 3307) How to do this?

  1. Define the spring.datasource.url property in the customer-service project:
    spring.datasource.url=jdbc:mysql://${DB_HOST:127.0.0.1}:${DB_PORT:3307}/${DB_NAME:customer_service_db}?createDatabaseIfNotExist=true
                            
  2. Build the docker image. Run the docker image using the following command:
    > docker run --name customer-service -p 8080:8080 -v "C:/Ilman/Projects/customer-service/logs":/app/logs -e DB_HOST=host.docker.internal  customer-service
    # It is important to specify the DB_HOST as 'host.docker.internal'. The volume is enclosed in double quotes because of a hyphen in the path. It wont work with single quotes
                            

When you want to communicate between a customer-service container and a mysql container, it is recommended to use the docker networking method given above. But, it is also possible to connect to the mysql docker from the customer-service docker by running the container with this argument -e DB_HOST=host.docker.internal

Imagine, you want to connect a customer-service (that is started from a JAR file) to a local mysql instance that is running on port 3307. How to do this?

  1. Define the spring.datasource.url property in the customer-service project as follows:
    spring.datasource.url=jdbc:mysql://127.0.0.1:3307/customer_service_db?createDatabaseIfNotExist=true
                            
  2. Compile the app:
    > mvn clean package -DskipTests
                            
  3. Run the app:
    > java -jar .\target\customer-service-0.0.1-SNAPSHOT.jar