Skip to content

Docker Compose

Docker Compose is a technology that allows you to locally run multi-container applications, i.e., one or more containers simultaneously. Creating a Docker Compose setup is a typical task for a DevOps engineer.

docker-compose is a plugin for Docker, as well as a file with a specifically defined notation. This plugin used to be a separate application, but Docker acquired it and integrated it with the rest of the system. docker-compose allows you to define containers that need to be started, dependencies between them, networks, etc.

docker-compose can be considered your first container orchestrator. An orchestrator is additional software that helps manage containers when there are many of them, or to configure some non-standard behavior.

Structure of docker-compose

docker-compose is a YML file. The notation of such a file is quite simple and somewhat similar to JSON, but without braces, for example: docker-compose.yml

version: '3'
networks:
services:
volumes:

In this file, you first write a field, then a colon, then the field value. In the example, you can see the following fields:

  • version — the version of the docker-compose plugin;
  • networks — an optional field that describes Docker networks;
  • services — the description and configuration of which container to run and which configurations to apply;
  • volumes — volumes that can be attached in the services section.

Adding a network

Now let's make it so that a Docker network is created from docker-compose, which the containers will use:

networks:
db-data-net:
    driver: bridge

Under the networks section, add a tab and write the network name. Be sure to put a colon so you don't break the syntax. Below, write the network driver type.

💡 bridge is the network type that Docker uses by default. Containers connected to this type of network will receive an IP address allocated from the common IP pool of that network. Containers connected to the same bridge network can communicate with each other using the internal IP address or container name. 💡 We may not know in advance what specific IP a container will receive, so the interaction between applications in different containers should be organized using domain names (the container name will serve as the domain name).

Adding a volume

First, let's define our volume in the corresponding section:

volumes:
 db-data:

Volumes have different driver types. We will use the default driver type — local, which allows creating volumes on the container's host machine. To use the default driver type, you don't need to specify anything additional.

Adding a database

Let's add a database to services:

 mysql:
   image: my-sql:1.2
   build:
     context: .
     dockerfile: Dockerfile.mysql
   container_name: my-sql
   ports:
     - "3306:3306"
   volumes:
     - db-data:/var/lib/mysql
   environment:
     - MYSQL_ROOT_PASSWORD=1234
     - MYSQL_DATABASE=app_db
     - MYSQL_USER=app_user
     - MYSQL_PASSWORD=1234
   networks:
     - db-data-net

Here: - my-sql:1.2 — the Image name to download from Docker Hub or find locally. - build — instructions that will be executed if the Image is not found locally. In other words, we describe the command, set the context and the path to the Dockerfile relative to docker-compose. - volumes - we specify the driver and the path where to mount in the system - ports - port mapping - environment - we define environment variables. - networks - we specify which network to use from the general networks section

Adding a Python application

For our application, we will set:

  1. Image, and if it doesn't exist, then build parameters.
  2. Port mappings.
  3. The environment variable that we defined earlier in the Dockerfile for the Python application.
  4. Network.
  5. The depends_on parameter, which tells docker-compose to initiate the database startup first, but it will not wait for it to fully start, so we also add a restart policy.
 pythonapp:
   image: app:1.2
   build:
     context: .
     dockerfile: Dockerfile.app
   ports:
     - "8080:8080"
   environment:
     - APP_ENV=Development
   networks:
     - db-data-net
   depends_on:
     - mysql
   restart: unless-stopped

We also modify the connection string in the application:

mysql+mysqlconnector://app_user:1234@172.17.0.2/app_db
There are two reasons why having a hardcoded container IP is not very convenient: 1. The IP may change. 2. We are running two containers at once. We will use the container name for the database:
mysql+mysqlconnector://app_user:1234@my-sql/app_db

Time to run it all, but first let's clear all cache:

docker system prune -a

In the terminal, navigate to the directory with docker-compose and run the following command:

docker-compose up -d

The -d parameter is equivalent to --detach and means that log output will be hidden. If you don't specify this parameter, the logs of both containers will be shown in the console.

Container status:

docker compose ps
Container logs:
docker compose logs
To stop all containers:
docker-compose down
You can restart in one line:
docker-compose down; docker-compose up -d