Docker compose lets you define and run multi-container docker applications. In the last article, I wrote about how to containerize a simple standalone spring boot application that doesn’t have a dependency on any other service or database.
But, In real-world, you’ll have applications that interact with a database and also depend on other services. So essentially, you’ll have multiple containers that interact with each other.
Docker compose lets you deploy and manage apps involving multiple containers. It significantly improves the development and testing workflows wherein you can quickly run and test the whole stack together.
Note that, docker compose runs all the containers on a single host by default. But you can also use compose against a Swarm instance and run your apps across multiple hosts.
In this article, you’ll learn how to run multi-container apps on a single host using docker compose. Check out this article if you want to learn how to deploy your apps across a swarm cluster.
Building the docker images
In this article, we’ll run a full-stack application built using Spring Boot, React, and MySQL.
You can check out the complete code of the application on this github repository ). It is a Polling app where users can login, create a Poll, and vote for a Poll.
Please clone the application locally before proceeding further.
Dockerfile for the Spring Boot application
The backend of the application (polling-app-server
) is written in Spring Boot. Here is the Dockerfile for the spring boot app:
#### Stage 1: Build the application
FROM openjdk:8-jdk-alpine as build
# Set the current working directory inside the image
WORKDIR /app
# Copy maven executable to the image
COPY mvnw .
COPY .mvn .mvn
# Copy the pom.xml file
COPY pom.xml .
# Build all the dependencies in preparation to go offline.
# This is a separate step so the dependencies will be cached unless
# the pom.xml file has changed.
RUN ./mvnw dependency:go-offline -B
# Copy the project source
COPY src src
# Package the application
RUN ./mvnw package -DskipTests
RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)
#### Stage 2: A minimal docker image with command to run the app
FROM openjdk:8-jre-alpine
ARG DEPENDENCY=/app/target/dependency
# Copy project dependencies from the build stage
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","com.example.polls.PollsApplication"]
Dockerfile for the React application
The frontend of the application (polling-app-client
) is written in React. We’ll deploy the React app behind an nginx server.
Following is the Dockerfile for the React app (It uses nginx.conf available in the same directory.)
#### Stage 1: Build the react application
FROM node:12.4.0-alpine as build
# Configure the main working directory inside the docker image.
# This is the base directory used in any further RUN, COPY, and ENTRYPOINT
# commands.
WORKDIR /app
# Copy the package.json as well as the package-lock.json and install
# the dependencies. This is a separate step so the dependencies
# will be cached unless changes to one of those two files
# are made.
COPY package.json package-lock.json ./
RUN npm install
# Copy the main application
COPY . ./
# Arguments
ARG REACT_APP_API_BASE_URL
ENV REACT_APP_API_BASE_URL=${REACT_APP_API_BASE_URL}
# Build the application
RUN npm run build
#### Stage 2: Serve the React application from Nginx
FROM nginx:1.17.0-alpine
# Copy the react build from Stage 1
COPY /app/build /var/www
# Copy our custom nginx config
COPY nginx.conf /etc/nginx/nginx.conf
# Expose port 80 to the Docker host, so we can access it
# from the outside.
EXPOSE 80
ENTRYPOINT ["nginx","-g","daemon off;"]
Creating the docker-compose.yml configuration
To deploy your application using docker compose, you need to create a docker-compose.yml
file that contains configuration for all the services in your entire stack.
Following is the docker-compose.yml
file for running our Polls app. It has three services: app-server
, app-client
, and db
. The app-server
contains configuration for the backend app, app-client
contains configuration for the react app, and db
is for the mysql database.
# Docker Compose file Reference (https://docs.docker.com/compose/compose-file/)
version: '3.7'
# Define services
services:
# App backend service
app-server:
# Configuration for building the docker image for the backend service
build:
context: polling-app-server # Use an image built from the specified dockerfile in the `polling-app-server` directory.
dockerfile: Dockerfile
ports:
- "8080:8080" # Forward the exposed port 8080 on the container to port 8080 on the host machine
restart: always
depends_on:
- db # This service depends on mysql. Start that first.
environment: # Pass environment variables to the service
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/polls?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false
SPRING_DATASOURCE_USERNAME: callicoder
SPRING_DATASOURCE_PASSWORD: callicoder
networks: # Networks to join (Services on the same network can communicate with each other using their name)
- backend
- frontend
# Frontend Service
app-client:
build:
context: polling-app-client # Use an image built from the specified dockerfile in the `polling-app-client` directory.
dockerfile: Dockerfile
args:
REACT_APP_API_BASE_URL: http://127.0.0.1:8080/api
ports:
- "9090:80" # Map the exposed port 80 on the container to port 9090 on the host machine
restart: always
depends_on:
- app-server
networks:
- frontend
# Database Service (Mysql)
db:
image: mysql:5.7
ports:
- "3306:3306"
restart: always
environment:
MYSQL_DATABASE: polls
MYSQL_USER: callicoder
MYSQL_PASSWORD: callicoder
MYSQL_ROOT_PASSWORD: root
volumes:
- db-data:/var/lib/mysql
networks:
- backend
# Volumes
volumes:
db-data:
# Networks to be created to facilitate communication between containers
networks:
backend:
frontend:
Notice the depends_on
keyword in the compose file. Docker compose will make sure that all the dependencies of a service is started first before starting the service itself.
Running the complete stack with docker compose
Well, you can now bring up the entire application with just one command:
$ docker-compose up
Starting spring-security-react-ant-design-polls-app_db_1 ... done
Starting spring-security-react-ant-design-polls-app_app-server_1 ... done
Starting spring-security-react-ant-design-polls-app_app-client_1 ... done
## ...
You see, it’s super easy to just run all the services using docker compose. Any new developer in your team can just run this command and start playing with your application without any setup whatsoever.
You can also stop all the services at once using docker compose using the following command:
$ docker-compose down
That’s all folks. Thank you for reading. See you in the next article.