Managing Docker Containers with Docker Compose

Posted on 303 views

Before we start to talk about docker-compose, ensure you have installed Docker and Docker Compose.

If your project/app uses multiple containers then it will be unreasonable trying to run the individual containers separately and then linking them. It is possible but tiresome, brings complexity in scaling and not one of DevOps recommendations. So here comes docker-compose to the rescue. Docker-compose helps you to run multiple containers, linking them and defining various container properties in one file. This file is called docker-compose.yml.

Docker-compose file

This file contains all the defined container properties and their images to be used in a specified project/app. It has changed over the years from version 1 to version 3. So it is a requirement to specify which version you are using at the start of each docker-compose.yml file. Docker-compose files are written in YAML format and are in the format of docker-compose.yml. A typical docker-compose file looks like this:

version: "3"
    image: postgres
      POSTGRES_USER: zuri
      POSTGRES_DB: zuri
      POSTGRES_PASS: zuri1234
      - pgdata:/var/lib/posgresql/data
      context: .
      - "8000:8000"
      - ./zuri:/zuri
    command: python runserver
      - db

The docker-compose file consists of various components. I won’t talk about all of them but I will touch on some important ones. Essentially the compose file defines services that control the containers. From the above example you can see I have two services, db and zuri. The db service is my database container while the zuri service is my project. The two communicate via the property depends_on. Which tells docker-compose to create the service should it not exist when running zuri service.

Under each service you can define many properties like ports to expose, volumes to use, network e.t.c. Of course, each container uses an image and therefore you can directly define the image in the docker-compose file or specify a Dockerfile to be used. In this example, you can see in zuri service I have defined build context with a . this tells docker-compose to look for a docker file in the same directory to build an image for the service. But the database service has the image Postgres directly defined.

Here is the zuri service dockerfile:

#base image
FROM python:3

LABEL Author="CodeGenes"

# The enviroment variable ensures that the python output is set straight
# to the terminal with out buffering it first

#directory to store app source code
RUN mkdir /zuri

#switch to /app directory so that everything runs from here

#copy the app code to image working directory
COPY ./zuri /zuri

#let pip install required packages
RUN pip install -r requirements.txt

Docker container lifecycle

Docker containers are ephemeral, that is, they are deleted when you delete the container. Their status depends on the status of the applications they host. The various states include:

1. Create container $ docker create –name

2. Run container $ docker run -it -d –name

3. Pause container $ docker pause

4. Unpause container $ docker unpause

5. Start container $ docker start

6. Stop container $ docker stop

7. Restart container $ docker restart

8. Kill container $docker kill

9. Remove/delete container $docker rm

Docker-compose commands

Compose of course being that it manages the containers, it also contains the various container commands to manage the whole container lifecycle. Some of the commands include start, stop, build view e.t.c

To view these commands and what they do, run this on your terminal:

$ docker-compose --help
Define and run multi-container applications with Docker.

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

  -f, --file FILE             Specify an alternate compose file (default: docker-compose.yml)
  -p, --project-name NAME     Specify an alternate project name (default: directory name)
  --verbose                   Show more output
  --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 (for example if your docker host
                              is an IP address)
  --project-directory PATH    Specify an alternate working directory
                              (default: the path of the Compose file)

  build              Build or rebuild services
  bundle             Generate a Docker bundle from the Compose file
  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 the Docker-Compose version information

Docker-compose run, up, exec – what’s the difference?

Well, when I started to use docker, I would notice that the tutorials often used docker-compose up, docker-compose run and docker-compose exec mostly without explaining. I know you can simply read the difference in the docs of the help command line but sometimes it is helpful to fully appreciate the difference by digging a little deeper.

So which one should you use and at what point? I will illustrate these differences using my Django docker project on GitHub.

Docker Compose Up

This command is used when you want to start or bring up all services in your docker-compose.yml file. Docker-compose.yml file defines your services, their properties, variables, and dependencies.

$ docker-compose up

To view containers running:

$ docker-compose ps

You can also tell docker-compose to run only one service e.g.

$ docker-compose up zuri

Docker Compose Run

This command will spin up a new container for you to use. It is mostly used when you want a new container or the container is not running and is a one-off process which avoids conflicts with other services in your docker-compose.yml  that might be running.

$ docker-compose run zuri python migrate

Docker Compose Exec

This command is used when you want to interact with a container that is already running. It, therefore, requires to be run with a command argument. Before you exec the container must be in running state!

$ docker-compose exec zuri bash

That’s it for now. Peace Out!


Gravatar Image
A systems engineer with excellent skills in systems administration, cloud computing, systems deployment, virtualization, containers, and a certified ethical hacker.