Docker Compose: Deploying Spring Boot Microservices

In this tutorial, you will learn to design and deploy the multi-container based Spring Boot application using Docker compose.

To learn more about Docker, please check Docker Tutorials page.

Overview

In the previous tutorial, we learned how to Dockerize a simple Spring Boot-based application. As it was a small application we have handled each container individually using multiple docker commands. But in a real-world scenario, while developing production-grade software, their single host service interacts with many dependent services in Docker containers. So, it is practically impossible to manage each container manually via docker commands. There is a need to manage containers like a coherent whole. Here Docker Compose comes to the rescue.

Docker Compose is a very powerful tool that allows you to very quickly deploy applications with complex architecture. It is used to manage multiple containers that are part of the same application service. This tool offers the same capabilities as Docker but allows you to work with more complex applications.

In this article, I’ll show you how to host an application in a Docker container and smoothly manage them using docker-compose.

Preparation:

  • JDK 1.8
  • Intellij Idea for IDE
  • Maven
  • SpringBoot Application
  • Lombok plugin
  • Docker Desktop 

In this blog, I’ll host an application in a Docker container, which consists of two inter-dependent containers:

  • A container for Spring Boot Application
  • A PostgreSQL database container

In the end, we will manage the interplay between two containers using the single docker-compose file.

Step 1: Creating a Basic Spring Boot Application

You can use the Spring Initializr page to bootstrap your project. After importing the project into IDE, we will be creating the following sub-packages inside our main package ‘com.appsdeveloperblogs.dockercomposetutorial‘  :

  • Controller:  This package contains class ‘ItemController‘ with filename ‘ItemController.java’
  • Entity: This package contains item entity POJO structure with filename ‘Item.java’
  • Repository: This package contains JpaRepository interface implementation to perform CRUD operation with Postgres DB.
  • Service:  This package contains the service class with the filename ‘service.java’. 

In the end, the final project structure will look like this:

project structure

In the following sections, we will learn about each section in detail.

Maven Dependencies

The complete pom.xml file will be as below −

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.5</version>
        <relativePath/>
    </parent>
    <groupId>com.appsdeveloperblog.DockerComposeTutorial</groupId>
    <artifactId>Spring-boot-DockerCompose</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringBoot Docker Compose Tutorial</name>
    <description>SpringBoot Docker Compose Tutorial</description>
    <properties>
        <java.version>11</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>42.2.18</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

Entity Class

Entity package contains the POJO structure of the Item object.  The complete code for the Item.java file is as follows −
package com.appsdeveloperblogs.dockercomposetutorial.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Item {
    @Id
    @GeneratedValue
    private int id;
    private String name;
}

 

Controller Class

In this class, we will be annotating it with @RestController annotation. Hence it will handle our web requests like GET /items for retrieving item lists from Postgres DB. The complete code for the ItemController.java file is as follow −

package com.appsdeveloperblogs.dockercomposetutorial.controller;

import java.util.List;

import com.appsdeveloperblogs.dockercomposetutorial.service.ItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.appsdeveloperblogs.dockercomposetutorial.entity.Item;

@RestController
public class ItemController {
    
    @Autowired
    private ItemService itemService;
    
    @GetMapping("/items")
    public List<Item> items() {
        return itemService.findAll();
    }

}

 

Repository Class

This class extends the JpaRepository interface, as we are using PostgreSQL DB for persisting data. Hence, JpaRepository will by default provides us with generic methods like save(), findAll(), insert(), etc.
The complete code for the UserRepository.java file is as follow −

package com.appsdeveloperblogs.dockercomposetutorial.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.appsdeveloperblogs.dockercomposetutorial.entity.Item;

public interface ItemRepository extends JpaRepository<Item, Integer>{

}

 

Service Class

The class is annotated with the @Service annotation, here we write the business logic to store, retrieve and update the item. We will insert three test records in our Postgres tables at the start of the application.
The complete code for the ItemService.java file is as follow −

package com.appsdeveloperblogs.dockercomposetutorial.service;

import java.util.List;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.appsdeveloperblogs.dockercomposetutorial.entity.Item;
import com.appsdeveloperblogs.dockercomposetutorial.repository.ItemRepository;

@Service
public class ItemService {
    
    @Autowired
    private ItemRepository itemRepository;
    
    @Transactional
    @PostConstruct
    public void init() {
        itemRepository.save(new Item(1, "John"));
        itemRepository.save(new Item(2, "Alex"));
        itemRepository.save(new Item(3, "Sandra"));
    }
    
    public List<Item> findAll() {
        
        return itemRepository.findAll();
    }

}

Step 2: Creating a Docker file

Firstly, we create a file with the name Dockerfile at the root of our project structure with the contents shown below. This Dockerfile instructs docker on which format the application image needs to be created.

The content of Dockerfile is as follow:

FROM openjdk:11
ADD target/*.jar myapplication
ENTRYPOINT ["java", "-jar","myapplication"]
EXPOSE 8080

Step 3: Creating Docker Compose file

Eventually, we will create docker-compose.yml file.

Typically, an application consists of several components that depend on each other but can be run in isolation on different machines. For example, in our current Spring Boot application, we provide REST API with CRUD operation in PostgreSQL DB. Here we have two containers – the one that ensures the operation of the site via REST API, and the other one is responsible for running the database. For such usecase, it is convenient to use docker-compose – where we can handle all services in a single file.

The docker-compose.yml file is shown below:

version: '3'
services:
  myspringapp:
    image: myapplication
    build: .
    ports:
      - "8080:8080"
    restart: always
    depends_on:
      - mypostgres
    environment:
      - SPRING_DATASOURCE_URL=jdbc:postgresql://mypostgres:5432/mydb
      - SPRING_DATASOURCE_USERNAME=postgres
      - SPRING_DATASOURCE_PASSWORD=password
      - SPRING_JPA_HIBERNATE_DDL_AUTO=create
  mypostgres:
    image: postgres
    restart: always
    ports:
     - "5432:5432"
    environment:
     - POSTGRES_PASSWORD=password
     - POSTGRES_USER=postgres
     - POSTGRES_DB=mydb


 

Version:

This specifies to Docker Compose which version you want to use for composing the file. In our case, we will use version 3, which is currently the most used version.

Services:

The set of containers that need to be controlled by the docker-compose needs to be defined under the Services argument. We have defined two services with the names myspringapp and mypostgres respectively for two containers in our application. Then, you need to describe your container with various other arguments as required.

  • image:  It allows us to define the Docker image name we want to use.
  • build: This specifies the location of our Dockerfile, and . represents the directory where the docker file is located.
  • ports: This allows us to tell Docker Compose that we want to expose a port from our host machine to our container, and thus make it accessible from the outside.
  • restart: The containers being by definition single-process, if this one encounters a fatal error, it can be brought to stop. In our case, if the Postgres SQL server stops, it will restart automatically thanks to the argument restart: always.
  • depends_on: This allows to specify in which order to start and stop a container. For example, the Postgres SQL container needs to be up before the Spring application starts.
  • environment: The clause allows us to set up an environment variable in the container for individual containers to run.

Launch of the project

Now that the project is built, it’s time to launch it. This step of our work corresponds to the step when the command docker run is executed while working on individual containers without docker-compose.

As mentioned in the docker file, the server exposed port 8080 to serve client requests. Therefore, if you go to the browser at the address http://localhost:8080/items, it will display the data retrieved from the Postgres SQL database.

Let’s run some commands on the terminal.

First, we need to build our project using the standard maven command:

mvn clean install

Next, we need to create a containerized image for our application using a docker-compose command.

docker-compose build

it should display output as below:

output docker compose build

Now we need to run all our containers using the single command as below.

docker-compose up

If docker already has dependent images present locally then it will start directly else first it pulls images for the docker hub. As below, docker is pulling Postgres images from the internet which docker-compose could not find it locally. This is a single time activity.

docker compose up

If everything compiles successfully, the output will display the success message as below:

ation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
myspringapp_1  | 2021-11-13 11:56:30.070  INFO 1 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory f
or persistence unit 'default'
myspringapp_1  | 2021-11-13 11:56:30.888  WARN 1 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by
default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
myspringapp_1  | 2021-11-13 11:56:31.462  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http)
 with context path ''
myspringapp_1  | 2021-11-13 11:56:31.484  INFO 1 --- [           main] c.a.d.DemoApplication                    : Started DemoApplication in 8.107 secon
ds (JVM running for 8.849)

Now, all the containers are up and running, we’ll try accessing the RESTful service using Postman or browser.

We’ll access the endpoint as:

http://localhost:8080/items

As expected, it will display a page with a list of item records fetched from Postgres SQL DB.

 

Stopping Containers

In addition, to stop all containers, we can use the following commands.

docker-compose down

 

Conclusion

In this tutorial, we learned how docker-compose can help in running complex applications in distributed systems with minimal management. In other words, by using simple docker-compose.yml instructions you can build, scale, heal and run any number of containers easily.  I hope this tutorial was helpful to you.


Leave a Reply

Your email address will not be published. Required fields are marked *