Docker Compose, Revisited

Let’s do a bit of rehash: docker-compose is a tool for defining and running multi-container Docker applications. With docker-compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.

Here, we will revisit our containerized Flask and Redis services, fire them up with docker-compose, and then have our two services talk to each other.

When working with multiple containers, it can be difficult to manage the starting configuration along with the variables and links. Docker-compose is one orchestration tool for defining and running multi-container Docker applications.

Why Docker-Compose?

  • Orchestration!

  • Launch multiple containers with complex configurations at once

  • Define the structure of your app, not the commands needed to run it!

Using Docker-Compose

Using docker-compose is a three-step process:

  • Define images with Dockerfiles

  • Define the services in a docker-compose.yml as containers with all of your options (e.g. image, port mapping, links, etc.)

  • Run docker-compose up and it starts and runs your entire app

Three step process to use … a bit more to actually build.

Orchestrating Redis

The first thing to do is create a new Docker build context for our app - this is the collection of files and folders that goes in to the image(s) we build. Create a folder called redis-docker and directories called config and data within:

[isp02]$ mkdir redis-docker/
[isp02]$ mkdir redis-docker/config
[isp02]$ mkdir redis-docker/data

Next copy this redis config file into your config directory. We are going to put this into our Redis container, and it will allow us to customize the behavior of our database server, if desired.

[isp02]$ cd redis-docker
[isp02]$ wget -O config/redis.conf https://raw.githubusercontent.com/TACC/coe-332-sp21/main/docs/week09/redis.conf
[isp02]$ ls config/
redis.conf

Now in your top directory, create a new file called docker-compose.yml. Populate the file with the following contents, being careful to preserve indentation, and replacing the username / port placeholders with your own:

---
version: '3'
services:
    redis:
        image: redis:latest
        container_name: <your username>-redis
        ports:
            - <your redis port>:6379
        volumes:
            - ./config/redis.conf:/redis.conf
        command: [ "redis-server", "/redis.conf" ]

Start the Redis Service

Bring up your Redis container with the following command:

[isp02]$ docker-compose -p <your username> up -d

Take note of the following options:

  • docker-compose up looks for docker-compose.yml and starts the services described within

  • -p <your username> gives the project a unique name, which will help avoid collisions with other student’s containers

  • -d puts it in daemon mode (runs in the background)

Check to see if your Redis database is up and the port is forwarding as you expect with the following:

[isp02]$ docker ps
CONTAINER ID   IMAGE            COMMAND                  CREATED          STATUS           PORTS                    NAMES
aa1b2b6908a9   redis:5.0.0      "docker-entrypoint.s…"   58 seconds ago   Up 55 seconds    0.0.0.0:6080->6379/tcp   charlie-redis

[isp02]$ docker logs aa1b2b6908a9
1:C 31 Mar 2021 20:14:45.615 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 31 Mar 2021 20:14:45.615 # Redis version=6.2.1, bits=64, commit=00000000, modified=0, pid=1, just started
1:C 31 Mar 2021 20:14:45.615 # Configuration loaded
1:M 31 Mar 2021 20:14:45.618 * monotonic clock: POSIX clock_gettime
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 6.2.1 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6080
 |    `-._   `._    /     _.-'    |     PID: 1
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           http://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'

1:M 31 Mar 2021 20:14:45.623 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
1:M 31 Mar 2021 20:14:45.623 # Server initialized
1:M 31 Mar 2021 20:14:45.623 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
1:M 31 Mar 2021 20:14:45.625 * Ready to accept connections

Boom! We have Redis running!

But Charlie! docker-compose is about defining and running multi-container Docker applications!

Add the Flask Service

First let’s take down the existing service:

[isp02]$ docker-compose -p <your username> down
Stopping charlie-redis ... done
Removing charlie-redis ... done
Removing network charlie_default

Note

It is assumed you are still in the same directory as your docker-compose.yml file, if not otherwise specified.

Next, add the following new lines to your docker-compose.yml file:

 ---
 version: '3'
 services:
     redis:
         image: redis:latest
         container_name: <your username>-redis
         ports:
             - <your redis port>:6379
         volumes:
             - ./config/redis.conf:/redis.conf
         command: [ "redis-server", "/redis.conf" ]
     web:
        build: .
        container_name: <your-username>-web
        ports:
           - <your flask port>:5000
        volumes:
           - ./data/data_file.json:/datafile.json

With these lines, you are adding a new service called ‘web’. Take care to replace the placeholders with your assigned Redis port and Flask port numbers. Note Redis and Flask use default ports 6379 and 5000, respectively, inside the containers unless otherwise specified.

Also new to this service, we are using the build key to build a new Docker image based on the files / Dockerfile in this (.) directory. We need to pull in our web assets (wherever they are located - it may be different for each person) and Dockerfile from our previous exercises to this current directory.

[isp02]$ mkdir web
[isp02]$ cp ~/coe-332/web1/app.py ./web/
[isp02]$ cp ~/coe-332/web1/requirements.txt ./
[isp02]$ cp ~/coe-332/web1/data_file.json ./data/
[isp02]$ cp ~/coe-332/web1/Dockerfile ./

# Now your directory structure should look like:
[isp02]$ tree .
.
├── config
│   └── redis.conf
├── data
│   └── data_file.json
├── docker-compose.yml
├── Dockerfile
└── web
    ├── app.py
    └── requirements.txt

This time when you start services, two containers will be created, one of which is built from the current directory.

[isp02]$ docker-compose -p charlie up -d
Creating network "charlie_default" with the default driver
Creating charlie-redis ... done
Creating charlie-web   ... done

Modify Python Redis Client

When you do docker-compose up, behind the scenes Docker creates a custom bridge network for each of your services to talk to one another. They can reach each other using the name of the service as the ‘host’, e.g.:

>>> rd = redis.StrictRedis(host='redis', port=6379, db=0)

Exercise

Connect your Flask container and your Redis container together using docker-compose, and curl the various endpoints to make sure it works.

Note

Be sure to change your Redis connection in your Flask App!