An Overview of Essential Docker Compose Commands and Their Usage

Working with applications that require multiple containers can be complex, but Docker Compose simplifies this process by allowing you to manage and configure them easily.

By using a YAML file, you can define your services, making it straightforward to set up interactions between containers.

In this tutorial, you'll learn:

  • The essential components that form the Docker Compose YML file
  • Common Docker Compose commands to deploy and manage your multi-container applications

What is docker compose?

Docker compose is a tool that allows you to manage applications consisting of multiple Docker containers. Instead of having multiple Dockerfiles and running and linking one by one with Docker, you define a docker-compose.yml file with the configuration you want and run it. This will create all the necessary services for your application. It also works in development, production, staging or testing environments, as well as with continuous integration services.

Structure of a docker-compose.yml file

Just as Dockerfile in Docker, where you configure the state of a container in a declarative way, in Docker compose, there is an equivalent: YML files.

Before you start with the commands, let’s see the structure of a docker-compose configuration file and some common guidelines.

A docker-compose file is simply a file with .yml extension and format. It doesn't exist by default, you have to create it.

These yml files are incredibly simple to understand.

services:
  <service-name>:
    <configuration-variable>:
      <values>
    <configuration-variable>:
      <values>
name-of-other-<service>:
    <configuration-variable>:
      <values>

📋
In earlier versions of docker compose, docker-compose.yml file started by specifying the version of docker compose to be used but since v2 it is no longer required.

The services section is nested immediately. There can be as many services as you want; web framework, web server, database, documentation, cache, etc. Each service will have its own configuration variables and their respective values.

That’s all, as simple as that.

Service names

The name you use for each service in our yml file will serve as a reference for its use in other services.

For example, if a service is called “db”, this is the name you should use in other applications to refer to a host or location.

# settings.py in Django
DATABASES = {
    'default': {
        # ...
        'HOST': 'db',
        # ...
    }
}

image

The image configuration sets the docker image from which the service will be generated. Ideal if our service does not need very complicated customization.

services:
  db:
    image: postgres

build

In case you need a custom image, it will probably be better to use a Dockerfile. The build option allows us to indicate the directory where it is located.

context and dockerfile

You can also write a custom Dockerfile, instead of the default one, specifying in context the place where it is located and in dockerfile its name. This is quite useful because it allows us to specify different files for production or development.

command

Command overwrites the container’s default command. This option is ideal for executing a command when starting a service, for example, a web server.

web:
  build: .
  command: python manage.py runserver 0.0.0.0:8000

ports

Ports tells us the ports that you will expose to the outside and to which port of your machine they will be linked. Always in the format of HOST:CONTAINER.

web:
  build: .
  command: python manage.py runserver 0.0.0.0:8000
  ports:
    - "80:8000"

In the code above, port 80 of our machine will correspond to port 8000 of the container. Remember, HOST:CONTAINER.

You can also specify the udp or tcp protocol.

services:
  web:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    ports:
      - "80:8000/udp"

expose

Expose also exposes ports; the difference with ports is that the ports will only be available for the linked services, not for the machine from where you are running docker-compose.

services:
  redis:
    image: redis
    expose:
      - '6379'

depends-on

Sometimes you want one of your services to run only after another.

For example, for a web server to work properly, it is necessary to have a database that is already running.

depends-on allows you to make the start of the execution of a service depend on others. In simpler words, it tells docker-compose that you want to start the web service only if all other services have already been loaded.

services:
  web:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    depends_on:
      - db
      - redis

In the above example, docker-compose will run the web service only if the db and redis services are already available.

environment

The environment configuration allows you to set a list of environment variables that will be available in your service.

services:
  db:
    image: postgres
    environment:
      - POSTGRES-USER=user
      - POSTGRES-PASSWORD=password

You can also use a dictionary syntax instead of the above.

services:
  db:
    image: postgres
    environment:
      MODE: development
      DEBUG: 'true'

Secret environment variables

If you do not specify a value for an environment variable and leave it blank, docker-compose will take it from the machine where docker-compose is running.

services:
  db:
    image: postgres
    environment:
      MODE: development
      DEBUG: 'true'
      SECRET-KEY:

This way, you don’t have to expose sensitive information if you decide to share your Docker compose files or store them in a version control system.

env-file

If you want to load multiple environment variables, instead of specifying the variables one by one, you will use env-file.

Consider that the env-file directive loads values into containers. So, these variables will not be available when creating the container. For example, you cannot put the PORT variable in an env-file and then condition the port that exposes your service.

# THIS IS NOT POSSIBLE
expose:
  - ${PORT}

Environment variables with .env

Docker compose automatically loads a file named .env from the root of the project and uses its environment variables in the configuration of its services.

# Possible with a .env file containing PORT=8009
expose:
  - ${PORT}

healthcheck

This command is to periodically check the status of a service. That is to say, you can create a command that allows you to know if your container is running correctly.

With the configuration below, Healtcheck will run a curl to localhost, every minute and a half, once 40 seconds have passed, if the command takes more than 10 seconds to return a result it will consider it as a failure and if a failure occurs more than 3 times the service will be considered “unhealthy”.

services:  
  web:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    env-file: common.env
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost"]
      interval: 1m30s
      timeout: 10s
      retries: 3
      start-period: 40s

volumes

You can send parts of your operating system to a service using one or several volumes. For this, use the syntax HOST:CONTAINER.

Host can be a location on your system or also the name of a volume you have created with docker.

services:  
  db:
    image: postgres:latest
    volumes:
      - "/var/run/postgres/postgres.sock:/var/run/postgres/postgres.sock"
      - "dbdata:/var/lib/postgresql/data"

Optionally, you can specify whether the use of volumes will be read-only or read-write, with “ro” and “rw”, respectively.

services:  
  db:
    image: postgres:latest
    volumes:
      - "/var/run/postgres/postgres.sock:/var/run/postgres/postgres.sock"
      - "dbdata:/var/lib/postgresql/data:ro"

restart

With restart, you can apply restart policies to your services.

services:  
  db:
    image: postgres:latest
    restart: "on-failure"

The restart option can take several values

  • no: never restarts the container
  • always: always restarts it
  • on-failure: restarts it if the container returns an error status
  • unless-stopped: restarts it in all cases except when stopped

Essential Docker compose commands

For a long time, Docker Compose existed in the form of a Python script that could be accessed with docker-compose. But recent versions of Docker in the last few years have started providing a built-in docker compose tool. Both tools are similar in terms of syntax and since docker-compose is considered deprecated, we have covered docker compose here.

Now that you have seen how a docker-compose file is conformed and some of its most common configurations, let’s start with the commands.

Compiling a service file

To build a docker-compose file, just run build command. This command will look for a file named docker-compose.yml in the current directory.

docker compose build
#Building postgres
#Step 1/4 : FROM postgres:12.3
# ---> b03968f50f0e
#Step 2/4 : COPY ./compose/production/postgres/maintenance /usr/local/bin/maintenance
# ---> Using cache
#...

If you want to specify a specific docker-compose file, use the -f option, followed by the file name.

docker compose -f production.yml build

Running docker-compose and its services

Once the image with your services has been created, you can start and create the services with the up command. docker-compose up will start or restart all the services in the docker-compose.yml file or the one we specify with -f.

docker compose up
#Creating network "my-project" with the default driver
#Creating postgres ... done
#Creating docs     ... done
#Creating django   ... done

You will probably want to run your stack of services in the background. For that just add the -d option at the end.

docker compose up -d

Run a command inside a running container

To execute a command inside a running service, use the command docker-compose exec, followed by the name of the service and the command. In this case, when running bash. you enter the terminal of our service named app.

docker compose exec app bash

Stop and remove services

Stops and removes containers, networks, volumes and images that are created with the docker-compose down command.

docker-compose down
#Stopping django   ... done
#Stopping docs     ... done
#Stopping postgres ... done
#Removing django   ... done
#Removing docs     ... done
#Removing postgres ... done
#Removing network my_project

Restart services

If you want to restart one or all services, use the command docker-compose restart.

docker compose restart
#Restarting django   ... done
#Restarting docs     ... done
#Restarting postgres ... done

To execute docker-compose restart on a single service, just put the name of the service at the end.

docker compose restart <service>

Stop the services without removing them

To stop one or all services, use docker-compose stop.

docker compose stop
#Stopping django   ... done
#Stopping docs     ... done
#Stopping postgres ... done

To execute docker-compose stop only to a service, just put the name of the service at the end.

docker compose stop <service>

Starting docker-compose services without creating them

You can start one or all services with docker-compose start. This command is useful only to restart containers previously created but stopped at some point, and it does not create any new containers.

docker compose start
#Starting postgres ... done
#Starting django   ... done
#Starting docs     ... done

Adding the name of a service at the end, the docker-compose start command will execute on that service only.

docker compose start <service>

Running a command within a service

To execute a command inside one of your services, use the run command.

The --rm option will delete the container that will be created when it finishes executing.

docker compose run --rm django python manage.py migrate
📋
Unlike docker-compose start, this command is used to perform one-time tasks.

See the processes

To list the running containers:

docker compose ps
#  Name Command State Ports         
#-------------------------------------------------------------------------
#django     /entrypoint /start Up 0.0.0.0:8000->8000/tcp
#docs       /bin/sh -c make livehtml Up 0.0.0.0:7000->7000/tcp
#postgres docker-entrypoint.sh postgres Up 5432/tcp

To list a single container, you place it at the end of your command.

docker compose ps <service>

Access to processes

In the same way as the top command in Linux, docker-compose top shows us the processes of each of your services.

docker compose -f local.yml top
#django
#UID PID PPID C STIME TTY TIME CMD                               
#---------------------------------------------------------------------------------------------------------------------
#root 29957 29939 0 20:09   ?     00:00:00   /bin/bash /start
#...

To see the processes of a single service just type its name at the end of the command docker-compose top

docker compose top <service>

View logs

If something goes wrong, you can view the logs using docker-compose logs. If you want to see the logs of a specific stack just set your yml file with the -f. option.

docker compose -f production.yml logs
#Attaching to django, docs, postgres
#django      | PostgreSQL is available
#django      | Operations to perform:
#...
#postgres    | PostgreSQL Database directory appears to contain a database; Skipping initialization
#...
#docs        | sphinx-autobuild -b html --host 0.0.0.0 --port 7000 --watch /app -c . ./_source ./_build/html

As with the other commands, if youe want to read the logs of a service, it is enough to add its name at the end.

docker compose -f production.yml logs <service>

Scaling containers

Previously, the docker-compose scale command was used to scale services. In the new versions of docker-compose scaling containers is done with the command docker-compose up.

After the command, you add the --scale option followed by the service you want to scale and the number of copies using the format service=number.

docker compose -f production.yml up -d --scale <service>=3

You must take into account that when you scale a container, it will try to create another container with a port that will already be in use, which will cause a conflict, for this reason you need to specify port ranges in our docker compose file. You also cannot use container names in your services, so you will have to remove them.

Wrapping Up

Docker Compose is a powerful tool for managing multi-container applications, simplifying the configuration and execution of services in various environments.

By understanding the structure and commands of docker-compose.yml files, you can efficiently build, run, and maintain complex applications.

Experiment with these commands and configurations to streamline your development and deployment workflows.