Running Multiple Spring Boot Services with Docker Compose
Tags: docker, dockercompose, devops, springboot
Published: 2020-08-26 11:51:06

---
title: Running Multiple Spring Boot Services with Docker Compose
published: true
date: 2020-08-25 22:18:46 UTC
tags: docker, dockercompose, devops, springboot
canonical_url: https://www.briansdevblog.com/2020/08/running-multiple-spring-boot-services-with-docker-compose/
---

In this post we’ll look at how Docker Compose makes it easier to configure and run multiple containers in your local environment.

## Why Docker Compose?

First up, you don’t need Docker compose to run multiple containers. You can do this just fine by manually starting and stopping the containers yourself, as shown previously in this [post](https://www.briansdevblog.com/2016/08/docker-multi-container-app/).  However, as the number of containers in your application grows,  it becomes more cumbersome to manage each container manually.

Docker compose simplifies things by allowing you to configure a multi container application in a single YAML file. You can start and stop all containers in the application with a single command.

## Sample App Code

I’ve created a sample app for this post which you can pull from [Github](https://github.com/briansjavablog/boot-microservices-docker-compose). It contains the following

- 2 Spring Boot applications
  - Bank Account Service – exposes a REST API for creating and reading bank simple account details
  - Config Service – exposes a REST API with application configuration for the Bank Account Service
- 2 Dockerfiles – to define the container images for the above services
- A Docker compose file defining the multi container application

Aside form the Docker side of things, I wont go into any detail on the Boot services. If you want more info you can check out this previous [post](https://dev.to/briansjavablog/configuring-micro-services-spring-cloud-config-server-579m-temp-slug-867372).

## Bank Account Service Dockerfile

We’ll begin defining a Docker image for the Bank Account Service.

```
# MAINTAINER Brian HannawayFROM openjdk:8-jre-alpine
WORKDIR /app
# Add wait script to the image - script pulled from https://github.com/ufoscout/docker-compose-wait/releases/download/2.7.3/wait /wait
COPY /scripts/wait /app/RUN chmod +x /app
RUN apk --no-cache add curl
COPY /target/bank-account-service-0.0.1-SNAPSHOT.jar /app/
CMD ./wait && java -jar bank-account-service-0.0.1-SNAPSHOT.jar
```

`FROM openjdk:8-jre-alpine` tells Docker to use the openjdk:8-jre-alpine base image.

`WORKDIR /app` tells Docker to create a new working directory in the image called _/app_.  All further commands will run from this directory.

`COPY /scripts/wait /app/` tells Docker to copy the  `wait` script from the _scripts_ directory on the host to the _/app_ directory in the image. I’ll explain the purpose of the _wait_ script in detail later.

`RUN chmod +x /app` makes the contents of the _/app_ directory executable

`COPY /target/bank-account-service-0.0.1-SNAPSHOT.jar /app/` copies the service JAR from the target directory on the host to the _/app_ directory in the image

`CMD ./wait && java -jar bank-account-service-0.0.1-SNAPSHOT.jar` runs the  `wait` script, followed by the bank account service. The service won’t run until the `wait` script has finished.

## Config Service Dockerfile

Next we’ll define the Config Service Docker image. Its a slightly simpler version of the image we created for the Bank Account Service above. We’ll simply create a working directory, copy in the service JAR and run it.

```
FROM openjdk:8-jre-alpine
MAINTAINER Brian Hannaway
WORKDIR /app
COPY /target/config-server-0.0.1-SNAPSHOT.jar /app/
ENTRYPOINT ["java", "-jar", "config-server-0.0.1-SNAPSHOT.jar"]
```

## Defining the Docker Compose File

Now that we’ve defined Dockerfiles for the Bank Account and Config services, the next step to create a docker-compose file that describes how we’ll uses these images to run containers.

```
version: "3"

services: 
   config-service: 
      image: config-service 
      container_name: config-service 
      networks: 
         - micro-service-network 
      ports: 
         - 8888:8888 
   
   bank-service: 
      image: bank-service 
      container_name: bank-service 
      networks: 
         - micro-service-network 
      ports: 
         - 8080:8080 
      environment: 
         WAIT_HOSTS: config-service:8888

networks: micro-service-network:
```

`version: "3"` tells Docker that we’re using version 3 of the docker-compose file format. At the time of writing version 3 is the latest and recommended version. The docker-compose format version you use will be dictated by the version of Docker you’re running. I’m running Docker version 19.03.12 which means that I should be using version 3. If you want to check what version of docker-compose is compatible with your Docker version, check out this [compatibility matrix](https://docs.docker.com/compose/compose-file/compose-versioning/#compatibility-matrix).

### Services Definition

The `services` section defines the containers that make up your application. Each service definition contains all the configuration required to start a container from an image. The information in each service definition is what you’d typically supply on the command line, running a container manually.

```
config-service: 
   image: config-service 
   container_name: config-service 
   networks: 
      - micro-service-network 
   expose: - "8888"
```

### Config Service

The `config-service` section defines all the configuration docker needs to run the config-service container

`image` tells compose which image to use to run the container.

`container_name` is the name given to the container when it starts. If we don’t specify a name, compose will derive one based on the name of the compose file and the image name. For example, if I omit the name attribute for the `config-service` and run `docker-compose up` , I can see that the containers derived name is `boot-microservices-docker-compose_config-service_1`.

![](http://www.briansdevblog.com/wp-content/uploads/2020/08/containerDerivedName.png)

Generally its a good idea to give your containers a meaningful name. You’ll see later that we need to reference `config-service` from the `bank-service`. We’ll do this using the name specified in `container_name`.

`networks` defines the networks that the `config-service` container will join when it starts. In this instance it will join  `micro-service-network`, which we’ll define later.

`expose` lists the ports that are exposed on the container. The ports are exposed on either the default network or any network the container is attached to. The ports are not exposed to the host machine. To do this you’ll need to use the  `ports` attribute and supply the appropriate mappings.

### Bank Service

The `bank-service` definition is very similar to what we’ve already defined, with the `image`, `container_name`, `networks` and  `expose` attributes being similar to those defined for the `config-service`

```
bank-service:
   image: bank-service 
   container_name: bank-service 
   networks: 
      - micro-service-network 
   expose: - "8080" 
   environment: 
      WAIT_HOSTS: config-service:8888
```

The `environment` attribute is used to specify a list of environment variables for the container. In the `bank-service` we specify the environment variable `WAIT_HOSTS` and give it the value `config-service:8888`. In short, this  is required to control container start up order and ensure that the `config-service` is up and running before the `bank-service` starts. I’ll explain this in detail later.

### Networks Definition

The `networks` section allows you define a network for your services. For our application we defined a network called `micro-service-network`.   When you run `docker-compose up`, each container that starts will be added to the `micro-services-network` and will be visible to every other container in the application. Containers can reference one another via their host name, which is the same as the service name.  So in our sample application, the `banks-service` is able to access the `config-service` as `config-service:8888.`

If we don’t explicitly define a `network`, Docker will create one by default and add all services in the compose file to it.

## Running the Application

Running the `docker-compose up` command will

- create a bridge network called `micro-service-network`
- start a container using the `config-service` image. The container will expose port 8888 on `micro-service-network` and will be accessible to other containers via host name `config-service`.
- start a container using the `bank-service` image. The container will expose port 8080 on `micro-service-network` and will be accessible to other containers via host name `bank-service`.

It takes approximately 20 seconds for both the  `bank-service` and `config-service` to start. If you run `docker container ls` you should see the two containers that have just been created.

![](http://www.briansdevblog.com/wp-content/uploads/2020/08/containers_running.png)

## Service Dependencies & Startup Order

Its common to have dependencies between containers, such that a container A requires container B to be running before container A can start. Compose allows you to handle this scenario to a certain degree, by defining the startup order using the `depends_on` attribute. For example, the compose file below defines a `web` service and a `db` service, where `web` is dependent on `db`.

```
version: '3'

services: 
   web: 
      image: myWebApp 
      depends_on: 
         - db 
      db: 
         image: postgres
```

In the above example, compose will start the containers in dependency order, so `db` will be started before `web`. Although `depends_on` sets the order in which containers are started, it does not guarantee that Postgres, inside the `db` container is fully operational before the  `web` container starts.

We have a similar problem in our sample application because `bank-service` tries to call `config-service` on startup. If `config-service` isn’t fully stood up and available to take requests on port 8888, `bank-service` will fail. Using the `depends_on` attribute to start the `config-service` first, won’t guarantee that the `config-service` is fully operational before `bank-service` calls it.

### Introducing docker-compose-wait

[docker-compose-wait](https://github.com/ufoscout/docker-compose-wait/) is a great command line utility that solves the problem described above. When defining the `bank-service` earlier we made docker-compose-wait available to the image by copying the `wait` script into the `app` directory.

```
# Add wait script to the image - script pulled from https://github.com/ufoscout/docker-compose-wait/releases/download/2.7.3/wait /waitCOPY /scripts/wait /app/
```

We then told Docker to run the `wait` script along with the JAR when starting the container.

```
CMD ./wait && java -jar bank-account-service-0.0.1-SNAPSHOT.jar
```

When we defined `bank-service` in the docker-compose file, we included a `WAIT_HOSTS` environment variable that referenced `config-service` on port 8888. When we run  `docker-compose up`, the `wait` script pings `config-service` on port 8888. It will not allow `bank-service` container to start until `config-service` is up and running on port 8888.

We can see this in action in the log snippets below. The `wait` script checks if  `config-service` is available on port 8888, initially reporting that it isn’t available.

![](http://www.briansdevblog.com/wp-content/uploads/2020/08/docker-compose-wait-1.png)

Eventually `config-service` bootstraps and is up and running on port 8888. The `wait` script then reports _Host config-service:8888 is now available_ and the `bank-service` container is started.

![](http://www.briansdevblog.com/wp-content/uploads/2020/08/docker-compose-wait-2.png)

## Wrapping Up

In this post we looked at how docker-compose makes it easy to manage multiple containers in a simple single node environment. This is particularly useful for development environments and automated test environments. If you want to manage multiple containers in a multi node environment, then Docker Swarm is a better bet. We’ll look at Swarm in another post soon.

The post [Running Multiple Spring Boot Services with Docker Compose](https://www.briansdevblog.com/2020/08/running-multiple-spring-boot-services-with-docker-compose/) appeared first on [briansdevblog](https://www.briansdevblog.com).