What is Docker Compose? (with demo)

Learn what Docker Compose is, how it makes an engineer’s life easier and possible use cases


What is Docker Compose and how does it make life easier for an engineer like you?

This tutorial will answer that (and more) as simply as possible.

Agenda for this tutorial:

  • What is Docker Compose?
  • Why does it exist? how is it different from docker run,
  • How to use Docker Compose?
  • When to use it? – use cases

Brand new to Docker? Check out: What is Docker?

Want to learn more about Docker networking? Check out: Docker Networking Summary.

Ok…

Docker Compose is a tool for defining and running multi-container applications.

With Compose, you use a YAML file to configure your application’s services (containers). Then, with a single command, you can create, start, or delete your application services.

Running multiple containers is a very common scenario.

Take for example a WordPress (WP) application. It includes a WordPress service that talks to a MySQL database.

picture description

We can run both the containers using two containers docker run command with a bunch of cli arguments. db The container can be started like this:

docker run -d \
  --name db \
  --restart always \
  -v db_data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=supersecret \
  -e MYSQL_DATABASE=exampledb \
  -e MYSQL_USER=exampleuser \
  -e MYSQL_PASSWORD=examplepass \
  mysql:5.7
enter fullscreen mode

exit fullscreen mode

Additionally, we may need to isolate these containers from the host or other containerized applications. We can create or remove networks docker network order and modify docker run To take the network as an argument.

Typing these verbose commands once or twice can be fine. But as the number of containers and configurations increases, they become more difficult to manage.

With Compose, we define the configuration of the application only on the YAML file (name .) docker-compose.yml by default) like this:

version: '3.9'

services:
  db:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_DATABASE: exampledb
      MYSQL_USER: exampleuser
      MYSQL_PASSWORD: examplepass
      MYSQL_ROOT_PASSWORD: supersecret
    volumes:
      - db_data:/var/lib/mysql
  wordpress:
    image: wordpress
    restart: always
    ports:
      - 8080:80
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: exampleuser
      WORDPRESS_DB_PASSWORD: examplepass
      WORDPRESS_DB_NAME: exampledb
    volumes:
      - wordpress_data:/var/www/html

volumes:
  wordpress_data:
  db_data: 
enter fullscreen mode

exit fullscreen mode

This file defines 2 services, db And wordpress, It also specifies configuration options for each – such as image, environment variables, published port, volume, etc.

Note: If you haven’t figured out all of these options yet, don’t worry.

After creating this file, we execute docker compose upAnd Docker builds and runs our entire application in a new isolated environment (Bridge Network by default).

Similarly, we can use docker compose down The command to knock everything down (except the volume).

Easy! Correct? I

Thus Compose simplifies running multi-container applications on a single host.

Being able to declare and reuse the configuration as a file makes an engineer’s life a lot easier. It also allows us to version control, run tests, and review our configuration, just like the source code of the application.

Apart from the above benefits, Write provides the following key features:

  • Have multiple isolated environments on the same host
  • Preserve volume data when creating a container
  • Only recreate containers that have changed
  • Share variables or configurations between environments

It’s good to know all this. But let’s find out…

Source code for this demo: https://github.com/AluBhorta/docker-compose-demo

step 1: Install Docker and Docker Compose

First, make sure you have installed:

Note: Since Compose version 2, we use docker compose command instead docker-compose, This tutorial uses version 2.


step 2: Create a sample web application

Open a terminal, create a new directory and switch to:

mkdir docker-compose-demo
cd docker-compose-demo
enter fullscreen mode

exit fullscreen mode

Add code for a simple python web app to a file named app.py,

import time

import redis
from flask import Flask

app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)

@app.route("https://dev.to/")
def hello():
    count = cache.incr('hits')
    return 'Hello World! I have been seen {} times.\n'.format(count)
enter fullscreen mode

exit fullscreen mode

This creates a Flask app with a single HTTP endpoint (/) returns the endpoint of how many times it has been visited. The count is stored and incremented as an integer with a key hits in a redis host named redis,

Then we add the Python dependency to a . add to requirements.txt file:

flask
redis
enter fullscreen mode

exit fullscreen mode

After that, we a. make up Dockerfile – To build docker images based on this application:

FROM python:3.7-alpine

WORKDIR /code

RUN apk add --no-cache gcc musl-dev linux-headers

COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt

EXPOSE 5000

COPY . .

ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0

CMD ["flask", "run"]
enter fullscreen mode

exit fullscreen mode

This tells Docker:

  • Create an image starting with Python 3.7 Alpine Linux Image
  • set working directory /code
  • establish gcc and with other dependencies apk package manager
  • copy requirements.txt from host to image
  • install python dependencies with pip
  • Add metadata to the image to describe whether the container is on port 5000. listening on
  • copy current directory . workdir in project. for . in the image
  • Set environment variables to be used by flask command
  • Set default command for container flask run

Then we a. make up docker-compose.yml File for us to use Docker Compose:

version: "3.9"

services:
  redis:
    image: "redis:alpine"
  web:
    build: .
    ports:
      - "8000:5000"
    depends_on:
      - redis
enter fullscreen mode

exit fullscreen mode

This compose file defines two services: web And redis,

redis service uses a public redis:alpine Image pulled from the Docker Hub registry.

web service uses an image that is created from Dockerfile in current directory (.) it then maps the port 8000 port to host 5000 On the container where the flask server will be running. It also specifies that web depends on redis so that docker knows to start redis before this web,

Note: Since the Compose project creates a new bridge network at startup, web can reach redis By using the name of the bus service ie. redis,


Step 3: Run and test the application

To run the application, all we need to do is:

docker compose up
enter fullscreen mode

exit fullscreen mode

Docker will automatically pull redis image, build us web Start the image and the container.

picture description

Once deployed, we should now be able to access the application on localhost:8000 on your browser.

Or alternatively, use curl on a different terminal to access the Flask application:

curl localhost:8000
enter fullscreen mode

exit fullscreen mode

You should see something like this:

Hello World! I have been seen 1 times.
enter fullscreen mode

exit fullscreen mode

Every time we make a request the count is incremented.

Great! I

We can list the Compose project’s containers with:

docker compose ps
enter fullscreen mode

exit fullscreen mode

Comment: docker compose up By default will connect to your terminal and print logs from services. we can use ctrl+c to detach that terminal, but it will stop services.

To run services in background, use -d flag:

docker compose up -d
enter fullscreen mode

exit fullscreen mode

If you want to see the logs, use:

docker compose logs -f
enter fullscreen mode

exit fullscreen mode

Comment: -f Log output will follow as new logs are generated.


Step 4: Modify the Application

Changes are inevitable.

Let us make changes in our app.

web The container is printing a warning:

WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
enter fullscreen mode

exit fullscreen mode

In production, we definitely want to run a production-grade server like gunicorn instead of development server we get flask run,

So, let’s add first gunicorn To requirements.txt,

flask
redis
gunicorn
enter fullscreen mode

exit fullscreen mode

then remove the last 3 instructions of Dockerfile and add a new CMD Instructions:

FROM python:3.7-alpine

WORKDIR /code

RUN apk add --no-cache gcc musl-dev linux-headers

COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt

EXPOSE 5000

COPY . .

CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
enter fullscreen mode

exit fullscreen mode

images made of Dockerfile will work now gunicorn Instead of Flask Dev Server.

Once we make the change, we have to rebuild it web image:

docker compose build
enter fullscreen mode

exit fullscreen mode

then we restart web Service to use the new image:

docker compose up web -d --no-deps -t 1
enter fullscreen mode

exit fullscreen mode

Comment:

  • --no-deps Tells Docker not to start dependent services.
  • -t 1 Specifies a container shutdown timeout of 1 second instead of the default 10s.

try to reach web Again:

curl localhost:8000
enter fullscreen mode

exit fullscreen mode

It should work as expected.

But if you check the log:

docker compose logs web -f
enter fullscreen mode

exit fullscreen mode

You will notice that we don’t have any warnings anymore because we are using gunicorn.

Noise!

Step 5: Clean

To remove all services, we simply run:

docker compose down
enter fullscreen mode

exit fullscreen mode

If we had defined the volume in services (like in the WordPress example), the volume would not be removed automatically docker compose down, This is mainly to avoid accidental deletion of data.

To tear everything down, including the volume, use -v flag:

docker compose down -v
enter fullscreen mode

exit fullscreen mode

Congratulations! Now you can compose! I


  • ,development environment,

    When developing software, the ability to run applications and their dependencies in different environments is important. For example, you may depend on another team’s application, which in turn may have its own set of complications, such as configuring the database in a particular way. Using compose, you can walk the entire stack or delete it with a single command.

  • ,automated test environment,

    Automated workflows such as CI/CD pipelines require tools to easily create and destroy environments. Containers are ideal for such environments due to their low resource footprint and speed. Using a configuration file, compose provides a convenient way to create and destroy such environments for your test suite.

  • ,single host deployment,

    Although Compose was primarily developed for developing and testing workflows, it is sometimes used in production to run containers on a single host. While Compose is improving, it is not an orchestrator like Swarm or Kubernetes, but a wrapper around Docker’s API. Please refer to the official documentation before using Compose in Production.

In this tutorial, we learned what is Docker Compose, why it exists, how to use it and when to use it.

By specifying the container configuration in a file, Compose makes it easy to run multi-container workloads on a single host.

If you found this tutorial helpful, then liking, commenting or subscribing will really help us.

You can also enjoy our YouTube channel.

Thanks for making it this far!

We have a lot more exciting DevOps stuff on the way! I

See you on the next one.

Until then…

Be courageous and keep learning.

but most importantly,

take care!

Leave a Comment