Docker Compose Tutorial

docker-compose

At this point in our Docker Tutorial Series, we have learned how to get Docker installed, run some Nginx containers, tested out MongoDB in a container, spun up more than one container, learned about Docker networking, DNS, images, volumes, and much more. You may have noticed that when using the Docker command-line interface, the commands can be somewhat verbose and require a lot of memorization of what commands to run to accomplish all of the prior mentioned tasks. This is where Docker Compose comes to the rescue. By leveraging Docker Compose, you can define multi-container applications that provide a service, all by defining a single YAML File. Once your YAML file is complete, you can run a single command to bring up an application that may have multiple containers, a network, and one or more volumes as needed.


Why Use Docker Compose?

Docker Compose is a combination of a command-line tool, working in concert with a configuration file(the YAML file mentioned above). In order to use Docker Compose, you’ll want to be familiar with building images, running containers, creating networks, and how to use volumes. Software services are not usually in isolation. Containers are a single process, and most useful applications need much more than a single process to provide a useful service. A Node container is going to need other containers like perhaps a Mongo container or a Redis container to do useful work. Docker Compose gives you a way to connect all the pieces of a solution together, along with setting up the networking and volumes, without having to run an excessive amount of commands at the command line. It works using two things, the Docker Compose YAML file, and The Docker Compose CLI.


Docker Compose YAML File

The first part of Docker Compose is the YAML file. YAML stands for Yet Another Markup Language, and it is the format used for Docker Compose configuration files. In the YAML file, you would specify several things.

The YAML File Defines:

docker-compose.yml

The docker-compose.yml file has several versions, with version 3 being the most current and recommended at the time of this writing. The version statement should always be the very first line in the docker-compose.yml file. The default file name to use is docker-compose.yml, but you can specify a different name if you must by using the -f flag. In this file, you define the application stack and the way components in the stack interact with each other.

Sample docker-compose.yml template

version: '3.8' 
# Specify the compose format that this file conforms to.

services: 
# Specify the set of containers that your app is composed of.

    servicename1:
    # Same as 'docker container run'
    # Used as the DNS name inside the network.

        build: 
        # Build an image from a Dockerfile

        image: 
        # Tag or partial image ID. Can be local or remote, Compose will attempt 
        # to pull if it doesn't exist locally.
        # Optional if you use 'build:' command

        command:
        # Override the default command.
        # Optional to replace the default CMD specified by the image

        environment: 
        # Add environment variables. You can use either an array or a dictionary
        # Environment variables with only a key are resolved to their values on 
        # the machine Compose is running on. Same as '-e' in 'docker run'

        volumes: 
        # Same as using '-v' with 'docker container run'

        ports: 
        # Expose ports. Either specify both ports (HOST:CONTAINER)
        # or just the container port (a random host port will be chosen).

    servicename2:
        build: 
        image:
        command: 
        environment: 
        volumes: 
        ports: 
        
volumes: # Optional, same as 'docker volume create'
networks: # Optional, same as 'docker network create'

So you can see why you kind of need to be familiar already with images, containers, environment variables, networks, and volumes since these are exactly what you are defining in the compose YAML file. The ‘services’ section is where all of the containers of your app are defined. Inside ‘services’, we see ‘servicename1’ and ‘servicename2’. This would represent two different containers on the same network. Again, the name you choose here is also used as the DNS name on the virtual network, so choose your name carefully. Inside of each ‘servicename’, are the things that comprise each container. These of course are things like the image, command, variables, and volumes in use. We added comments above to explain what each key in the YAML file does, but typically the file will look more compact. Here it is without the comments, where you can see how the indentation sets up the hierarchy.

version:  
services: 
    servicename1:
        build:
        image: 
        command:
        environment: 
        volumes: 
        ports: 
    servicename2:
        build:
        image:
        command: 
        environment: 
        volumes: 
        ports: 
volumes: 
networks:

Docker Compose CLI

The second part of Docker Compose is the docker-compose cli tool. This tool parses the YAML configuration file to set up local development and test automation in the most common scenarios. Note that Docker Compose is constantly evolving, and may find use in production or other ways as well. Check the spec and the roadmap for more information on this. The CLI of Docker Compose greatly reduces the number of commands you need to memorize and type. You can go a long way with two simple commands.

  • docker-compose up
  • docker-compose down

Note: Be sure to type docker-compose and not docker compose, or you will get an error of Command “compose up” not available in current context (default).

The full listing of Docker Compose CLI commands is listed here:

PS C:\code\> docker-compose
Define and run multi-container applications with Docker.

Usage:
  docker-compose [-f ...] [options] [--] [COMMAND] [ARGS...]
  docker-compose -h|--help

Options:
  -f, --file FILE             Specify an alternate compose file
                              (default: docker-compose.yml)
  -p, --project-name NAME     Specify an alternate project name
                              (default: directory name)
  -c, --context NAME          Specify a context name
  --verbose                   Show more output
  --log-level LEVEL           Set log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
  --no-ansi                   Do not print ANSI control characters
  -v, --version               Print version and exit
  -H, --host HOST             Daemon socket to connect to

  --tls                       Use TLS; implied by --tlsverify
  --tlscacert CA_PATH         Trust certs signed only by this CA
  --tlscert CLIENT_CERT_PATH  Path to TLS certificate file
  --tlskey TLS_KEY_PATH       Path to TLS key file
  --tlsverify                 Use TLS and verify the remote
  --skip-hostname-check       Don't check the daemon's hostname against the
                              name specified in the client certificate
  --project-directory PATH    Specify an alternate working directory
                              (default: the path of the Compose file)
  --compatibility             If set, Compose will attempt to convert keys
                              in v3 files to their non-Swarm equivalent (DEPRECATED)
  --env-file PATH             Specify an alternate environment file

Commands:
  build              Build or rebuild services
  config             Validate and view the Compose file
  create             Create services
  down               Stop and remove containers, networks, images, and volumes
  events             Receive real time events from containers
  exec               Execute a command in a running container
  help               Get help on a command
  images             List images
  kill               Kill containers
  logs               View output from containers
  pause              Pause services
  port               Print the public port for a port binding
  ps                 List containers
  pull               Pull service images
  push               Push service images
  restart            Restart services
  rm                 Remove stopped containers
  run                Run a one-off command
  scale              Set number of containers for a service
  start              Start services
  stop               Stop services
  top                Display the running processes
  unpause            Unpause services
  up                 Create and start containers
  version            Show version information and quit

Awesome Compose

Ok let’s put the rubber to the road. How powerful is Docker Compose? Well, consider this. Let’s say you want to try Django. There are a lot of prerequisites to take care of. You have to have the right version of Python installed on your system, you need to install Django using pip, you need to configure all kinds of variables, you might even need to set up virtual environments. You can do it the long way following this tutorial. With Docker Compose it’s as easy as a few commands. A great place to get started with using Docker Compose is with the Awesome Compose github repository. This repository is a curated list of Docker Compose examples that provide a starting point for how to integrate different services using a Compose file and to manage their deployment with Docker Compose. Some of the environments included are angular, apache-php, django, flask, Minecraft, nginx, golang, react, node, express, vuejs, traefik, java, WordPress, and many more.

Let’s try a few examples for ourselves!

PS C:\code> git clone https://github.com/docker/awesome-compose.git
PS C:\code> cd awesome-compose/django
PS C:\code\awesome-compose\django> docker-compose up
Creating network "django_default" with the default driver
Building web
Step 1/8 : FROM python:3.7-alpine
3.7-alpine: Pulling from library/python
188c0c94c7c5: Pull complete
55578f60cda7: Pull complete
3d0ed0f04a02: Pull complete
98762f4040a9: Pull complete
2eeb36df0351: Pull complete
Digest: sha256:61582ae7cccd4c8e64c79fd559e70ef1dbb5a5bddc8fab51cabfad592e7d5bde
Status: Downloaded newer image for python:3.7-alpine
 ---> 4d91c1ce4cc8
Step 2/8 : EXPOSE 8000
 ---> Running in 5c5ddb2d943e
Removing intermediate container 5c5ddb2d943e
 ---> 915a9a087216
Step 3/8 : WORKDIR /app
 ---> Running in d9fea7cfae35
Removing intermediate container d9fea7cfae35
 ---> d6ec540549fe
Step 4/8 : COPY requirements.txt /app
 ---> 7f0a3dfca478
Step 5/8 : RUN pip3 install -r requirements.txt
 ---> Running in d38b7eee00eb
Collecting Django==3.0.7
  Downloading Django-3.0.7-py3-none-any.whl (7.5 MB)
Collecting environs==7.3.1
  Downloading environs-7.3.1-py2.py3-none-any.whl (11 kB)
Collecting pytz
  Downloading pytz-2020.1-py2.py3-none-any.whl (510 kB)
Collecting sqlparse>=0.2.2
  Downloading sqlparse-0.4.1-py3-none-any.whl (42 kB)
Collecting asgiref~=3.2
  Downloading asgiref-3.3.0-py3-none-any.whl (19 kB)
Collecting python-dotenv
  Downloading python_dotenv-0.15.0-py2.py3-none-any.whl (18 kB)
Collecting marshmallow>=2.7.0
  Downloading marshmallow-3.8.0-py2.py3-none-any.whl (46 kB)
Installing collected packages: pytz, sqlparse, asgiref, Django, python-dotenv, marshmallow, environs
Successfully installed Django-3.0.7 asgiref-3.3.0 environs-7.3.1 marshmallow-3.8.0 python-dotenv-0.15.0 pytz-2020.1 sqlparse-0.4.1
Removing intermediate container d38b7eee00eb
 ---> 658522092c7f
Step 6/8 : COPY . /app
 ---> 7b9155a033ae
Step 7/8 : ENTRYPOINT ["python3"]
 ---> Running in 5c4690dcbf9d
Removing intermediate container 5c4690dcbf9d
 ---> b23991bc8043
Step 8/8 : CMD ["manage.py", "runserver", "0.0.0.0:8000"]
 ---> Running in 82d8e32ead64
Removing intermediate container 82d8e32ead64
 ---> 026aa876dacf

Successfully built 026aa876dacf
Successfully tagged django_web:latest
WARNING: Image for service web was built because it did not already exist. To rebuild this image you must use docker-compose build or docker-compose up --build.
Creating django_web_1 ... done
Attaching to django_web_1
web_1  | Watching for file changes with StatReloader

Everything is built and ready to go. Visit http://localhost:8000/ in the browser.
docker django

Whoa! If you have done this before manually, you can appreciate how much was just accomplished so easily. As we visit the page, the container logs out the visits to the console seen below.

web_1  | [28/Oct/2020 18:13:48] "GET / HTTP/1.1" 200 16351
web_1  | [28/Oct/2020 18:13:48] "GET /static/admin/css/fonts.css HTTP/1.1" 200 423
web_1  | [28/Oct/2020 18:13:48] "GET /static/admin/fonts/Roboto-Bold-webfont.woff HTTP/1.1" 200 86184
web_1  | [28/Oct/2020 18:13:48] "GET /static/admin/fonts/Roboto-Light-webfont.woff HTTP/1.1" 200 85692
web_1  | [28/Oct/2020 18:13:48] "GET /static/admin/fonts/Roboto-Regular-webfont.woff HTTP/1.1" 200 85876
web_1  | Not Found: /favicon.ico
web_1  | [28/Oct/2020 18:13:48] "GET /favicon.ico HTTP/1.1" 404 1974

Let’s try Angular.

PS C:\code\awesome-compose> cd angular
PS C:\code\awesome-compose\angular> docker-compose up

docker angular

What about Vue?

PS C:\code\awesome-compose> cd vuejs
PS C:\code\awesome-compose\vuejs> docker-compose up

docker vuejs

I’ve been meaning to try Flask…

PS C:\code\awesome-compose> cd flask
PS C:\code\awesome-compose\vuejs> docker-compose up

docker flask

In the span of just a few minutes, we were able to spin up several different back end and front end frameworks with a small amount of effort. This is the power of Docker Compose.

Further Reading for Docker Compose


Docker Compose Tutorial Summary

  • Used to configure relationships between containers
  • Saves our docker container run settings in a single file
  • Creates simple docker-compose up environment startups
  • Consists of two parts
    • 1. A YAML file that defines:
      • containers
      • networks
      • volumes
    • 2. The docker-compose CLI used to process those YAML files
  • YAML file is used with docker-compose command for local builds
  • docker-compose --help can give you good tips
  • docker-compose.yml is the default filename but can be customized via docker-compose -f
  • Docker Compose CLI comes with Docker for Windows and Mac
  • Perfect for local development and testing
  • Two most common commands
    • docker-compose up sets up volumes, networks and starts all containers
    • docker-compose down stops all containers and removes containers, volumes, and networks
  • Test some of the most popular software available in three easy commands
    • git clone https://github.com/docker/awesome-compose.git
    • cd software-to-run (django, angular, flask, vuejs, etc..)
    • docker-compose up
  • Compose can also build your custom images
  • Will build them with docker-compose up if not found in cache
  • Great for complex builds that have lots of vars or build args