Docker’s popularity as a development tool is on the rise. Docker has breathed new life into the container movement. Developers like using it because it’s fast and easy-to-learn. It helps development teams share standard environments without worrying about wasting time and resources.

Developers can set up the desired environment in a Docker container, save the container as an image and share it easily with their development teams. The process works great for a single container. However, multi-container environments are harder to maintain. Docker Compose provides the solution.

With Docker Compose, developers can define a YAML file to set up the configuration for multiple services. Then they can start the multi-container services with a single command. It simplifies the process of working with multi-container applications.

Prerequisite

We are assuming, you have a basic understanding of Docker. Otherwise, look at How to Install and Use Docker on Ubuntu. The examples use WordPress, MySQL, Flask, and Python. However, no prior knowledge of these tools is necessary.

Docker Compose Process: At a Glance

Define Application Environment: Use Dockerfile to define the app environment to make it easily reproducible. Define Docker Compose Environment: Use docker-compose.yml to define the services in the application. Run Application: Use docker-compose up to run the multi-container application.

Example Docker Compose File

version: '3' services: db: image: mysql:5.7 volumes: - db_data:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: rootpassword123 MYSQL_DATABASE: wordpress MYSQL_USER: wordpress_user MYSQL_PASSWORD: wordpress_password wordpress: depends_on: - db image: wordpress:latest ports: - "8000:80" restart: always environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wordpress_user WORDPRESS_DB_PASSWORD: wordpress_password volumes: db_data:

If the above docker-compose.yml file is invoked with docker up, it will create a WordPress service that connects to a MySQL database service.

Docker Compose commands

You can use docker-compose –help to find the Docker Compose command

When to Use Docker Compose?

Currently, Docker is mainly used in development environments. Some of the popular uses of Docker Compose are:

1. Prototyping and Development

Application prototyping and development process are slowed down due to the lack of standard environments. Developers often have to waste time setting up the same environment multiple times. Also, reading guides to set up environment parameters is time-consuming.

Docker Compose simplifies the process. Once an environment is configured, development teams can share the Docker files across the organization. It can save an enormous amount of time wasted on configuration management issues.

2. Testing and Automating Processes

Continuous integration and continuous delivery (CI/CD) are becoming standard processes in today’s agile development environments. Automated testing is an important component of CI/CD. Docker Compose helps define the automated testing process. All the complications of starting new services can be neatly put into docker configuration files. Testers can use these files to fire up temporary services, run text scripts and destroy the services after collecting the test results. It saves time because manually starting services is time-consuming and error-prone.

3. Future Production Deployment

Docker is mainly used in development environments. However, as Docker functionalities become more robust, Docker will be used for more production-level work. Docker Compose can be a valuable tool for single host deployments.

Exercise: A Simple Web Application

Let’s try our hand at a simple python based web application to try out Docker Compose. We will use the Flask web framework to create an application that communicates with an in-memory database Redis to keep track of how many times the web application has been visited.

The directory structure will look like this:

simple_app ├── content │ ├── Dockerfile │ └── code │ ├── simple_app.py │ └── requirements.txt └── docker-compose.yml

The above directory structure is not necessary for a basic application. However, it shows how organizing information can be helpful for more efficient implementation of Docker Compose.

Step 1: Create Directory Structure and Files

Let’s create the directory structure and the necessary files:

$ mkdir simple_app $ mkdir simple_app/content $ mkdir simple_app/content/code $ touch simple_app/docker-compose.yml $ touch simple_app/content/Dockerfile $ touch simple_app/content/code/simple_app.py $ touch simple_app/content/code/requirements.txt

The touch command is just creating empty files. You can manually go into the folders and create the files.

Step 2: Web Application Code

The code folder contains the web application code. Put the following in simple_app.py file:

from flask import Flask from redis import Redis app = Flask(__name__) redis = Redis(host='redis', port=6379) @app.route('/') def hello(): count = redis.incr('hits') return '<b>Welcome to Docker Compose Lessons!</b><br><br>You have visited this site {} times.

'.format(count) if __name__ == "__main__": app.run(host="0.0.0.0", debug=True)

The above application creates a welcome page that displays the number of times the page has been visited. The visit counter is maintained in a Redis database. Redis uses port 6379 as its default listening port. Next, fill out the requirements.txt file:

flask redis

This will enable pip to install python dependencies on the web container. We will run pip as part of initializing our service.

Step 3: Dockerfile

Fill the simple_app/content/Dockerfile with the following code:

FROM python:3.6.3-jessie ADD ./code /code WORKDIR /code RUN pip install -r requirements.txt CMD ["python", "simple_app.py"]

The above Dockerfile achieves the following:

Creates an image from python:3.6.3-jessie. If it’s not available locally then it downloads it from Docker Hub. Copies elements in simple_app/content/code into /code on the container Set /code as the working directory on the container Uses pip to install the python dependencies Sets the default starting point for the container to run python simple_app.py.

Step 4: Docker Compose

Fill the simple_app/docker-compose.yml file with the following code:

version: '3' services: web: build: ./content ports: - "5000:5000" volumes: - ./content/code:/code redis: image: "redis:alpine"

The docker-compose.yml file defines two containers: web and redis. It uses Docker Compose version 3 format.

For the web service:

Builds the web service using simple_app/content/Dockerfile

Forwards port 5000 from the web container to host’s port 5000. Port 5000 is the default port for Flask applications.

Volume simple_app/content/code is mounted as /code on the container. It means that if you change anything in the simple_app/content/code, it will be reflected in /code folder on the web container.

For the redis service:

Uses the redis:alpine image from Docker Hub to create the redis service.

Step 5: Running Applications using Docker Compose

The application is ready for deployment. From the simple_app folder, run the following command:

$ docker-compose up

The output should start like this:

$ docker-compose up Building web Step 1/5 : FROM python:3.6.3-jessie 3.6.3-jessie: Pulling from library/python 85b1f47fba49: Downloading [===========> ] 12.43MB/52.6MB 5409e9a7fa9e: Download complete 661393707836: Downloading [===============> ] 13.71MB/43.23MB 1bb98c08d57e: Downloading [> ] 1.081MB/134.7MB ...

Once all the images are built and running, you should see the following:

Status: Downloaded newer image for redis:alpine Creating simpleapp_redis_1 ... Creating simpleapp_web_1 ... Creating simpleapp_redis_1 Creating simpleapp_web_1 ... done Attaching to simpleapp_redis_1, simpleapp_web_1 redis_1 | 1:M 21 Oct 02:06:33.639 * Ready to accept connections web_1 | * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit) web_1 | * Restarting with stat web_1 | * Debugger is active! web_1 | * Debugger PIN: 237-189-083

You can test the application by going to http://localhost:5000:. If you refresh the page a few times, it should reflect the number of visits. You can check the status of services or containers running:

$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 22852e0ad98a redis:alpine "docker-entrypoint..." 5 minutes ago Up 5 minutes 6379/tcp simpleapp_redis_1 d51739d0a3ac simpleapp_web "python simple_app.py" 5 minutes ago Up 5 minutes 0.0.0.0:5000->5000/tcp simpleapp_web_1

If you start a bash shell in simpleapp_web_1 (your container name may differ), you will be logged into the working directory /code:

$ docker exec -it simpleapp_web_1 bash root@d51739d0a3ac:/code# ls requirements.txt simple_app.py root@d51739d0a3ac:/code#

The /code directory should reflect the content of simple_app/content/code inside it as seen above (simple_app.py and requirements.txt).

If you update your simple_app.py’s line from:

return '<b>Welcome to Docker Compose Lessons!</b><br><br>You have visited this site {} times.

'.format(count)

To:

return '<b>Welcome to Docker Compose Lessons!</b><br><br>Are you intrigued? <br><br>You have visited this site {} times.

'.format(count)

It should reflect on http://localhost:5000:

Step 6: Shutting Down the Services

You can stop the application using:

$ docker-compose stop Stopping simpleapp_redis_1 ... done Stopping simpleapp_web_1 ... done

The mounted volumes will persist. You can remove the containers entirely including the volumes using the following command.

$ docker-compose down --volume Removing simpleapp_redis_1 ... done Removing simpleapp_web_1 ... done Removing network simpleapp_default

Congratulations! You have mastered the basics of Docker Compose.

Further Study

For further study, look at the following documentation:

References: