Docker

Docker Compose Tutorial

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

  1. Define Application Environment: Use Dockerfile to define the app environment to make it easily reproducible.
  2. Define Docker Compose Environment: Use docker-compose.yml to define the services in the application.
  3. 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

Docker Compose Commands

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.\n'.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:

  1. Creates an image from python:3.6.3-jessie. If it’s not available locally then it downloads it from Docker Hub.
  2. Copies elements in simple_app/content/code into /code on the container
  3. Set /code as the working directory on the container
  4. Uses pip to install the python dependencies
  5. 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.\n'.format(count)

To:

return '<b>Welcome to Docker Compose Lessons!</b><br><br>Are you intrigued? <br><br>You have visited this site {} times.\n'.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:

About the author

Zak H

Zak H. lives in Los Angeles. He enjoys the California sunshine and loves working in emerging technologies and writing about Linux and DevOps topics.