Docker-Compose is a command line tool for defining and managing multi-container docker containers as if they were a single service. Compose is written in python and can be installed with the Python pip command. With compose, we can run multiple docker containers just with a single command. It allows you to create a container as a service, great for your development, testing and staging environment.

In this tutorial, I will guide you step-by-step to use docker-compose. We will deploy ‘WordPress‘ with Nginx, MySQL, and PHP-FPM. Each service has its own container, and we will use images from the docker hub registry. I will show you how to create containers from docker images and manage all containers with docker-compose.

Step 1 - Install Docker

We will start from scratch, by installing docker and docker compose manually with the apt command.

 

Before we begin, update the Ubuntu repository and install latest updates:

sudo apt-get update
sudo apt-get upgrade

By default, docker is available in the Ubuntu repository, so we can continue to install it right away:

sudo apt-get install -y docker.io

When the installation is done, start docker and add it to start automatically at boot time:

systemctl start docker
systemctl enable docker

Now test your docker installation with the command below:

docker run hello-world

You will see hello-world from docker.

Step 2 - Install Docker-Compose

Docker Compose is a single binary file. The installation is straightforward. We’ll download the file to a directory that is in the system  PATH  and make it executable.

The Docker Compose package is available in the official Ubuntu 20.04 repositories, but it may not always be the latest version.

At the time of writing this article, the latest stable version of Docker Compose is 

1.25.5
. Before downloading the Compose binary, visit the Compose repository release page on GitHub and check if there is a new version available for download.

Use 

curl
 to download the Compose file into the 
/usr/local/bin
 directory:

sudo curl -L "https://github.com/docker/compose/releases/download/1.25.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

Once the download is complete, apply executable  permissions  to the file:

sudo chmod +x /usr/local/bin/docker-compose

To verify that the installation was successful, run the following command which will print the Compose version:

docker-compose --version

The output will look something like this:

docker-compose version 1.25.5, build b02f1306

That’s it! Docker Compose has been installed on your Ubuntu machine, and you can start using it.

Step 1. Starting up nginx-proxy to hook Docker and Nginx together

To get started, let’s start up the nginx-proxy container. This can be accomplished either by a single docker command, or using docker-compose. Let’s cover both.

To get started, create a Docker network

Before we get started, either way, we need to first create a Docker network that we will use to bridge all of these containers together.

$ docker network create nginx-proxy

From now on, we need to ensure that we’re always adding new containers to the nginx-proxy Docker network.

Installing nginx-proxy with Docker

$ docker run -d -p 80:80 --name nginx-proxy --net nginx-proxy -v /var/run/docker.sock:/tmp/docker.sock jwilder/nginx-proxy

or

Installing nginx-proxy with docker-compose

First, create a new docker-compose.yml file in the directory of your choosing (one titled nginx-proxy is a good idea), and copy in the following text:

version: "3"
services:
  nginx-proxy:
    image: jwilder/nginx-proxy
    container_name: nginx-proxy
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro

networks:
  default:
    external:
      name: nginx-proxy

And then run the following docker-compose command to get started.

$ docker-compose up -d

How nginx-proxy works to host multiple websites

As you can see from the code in both options, the container listens on port 80 and exposes the same port inside of the container. That allows all incoming traffic to flow though nginx.

You might be wondering what the /var/run/docker.sock:/tmp/docker.sock line accomplishes. Essentially, this gives any container access to the host’s Docker socket, which contains information about a variety of Docker events, such as creating a new container, or shutting one down.

This means that every time you add a container, nginx-proxy sees the event through the socket, automatically creates the configuration file needed to route traffic, and restarts nginx to make the changes available immediately. nginx-proxy looks for containers with the VIRTUAL_HOST variable enabled, so that’s critical to our operations moving forward.

Also important to note is the --net nginx-proxy line in the Docker command, and the networks: default: external: name: nginx-proxy block in the docker-compose.yml file. These establish that all containers will communicate over that Docker network.

Step 2. Adding a container to the proxy

Now that we have nginx-proxy running, we can start adding new containers, which will be automatically picked up and configured for. Because we covered it in the last Docker tutorial, and since it’s an easy implementation to try out, let’s use WordPress as an example.

Using Docker

Starting a WordPress container with a basic configuration is quite easy.

$ docker run -d --name blog --expose 80 --net nginx-proxy -e VIRTUAL_HOST=blog.DOMAIN.TLD wordpress

Take note of a few elements here. --expose 80 will allow traffic to flow into the container on port 80. --net nginx-proxy ensures we’re using the Docker network we created earlier. Finally, -e VIRTUAL_HOST=blog.DOMAIN.TLD flags nginx-proxy to create the proxy information and direct traffic arriving to the correct domain.

or

Using docker-compose

Start by creating a ‘docker-compose.yml’ file in an empty directory and copying in the following.

version: "3"

services:
   db_node_domain:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: PASSWORD
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: PASSWORD
     container_name: wordpress_db

   wordpress:
     depends_on:
       - db_node_domain
     image: wordpress:latest
     expose:
       - 80
     restart: always
     environment:
       VIRTUAL_HOST: blog.DOMAIN.TLD
       WORDPRESS_DB_HOST: db_node_domain:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: PASSWORD
     container_name: wordpress
volumes:
    db_data:

networks:
  default:
    external:
      name: nginx-proxy

Again, take note of the expose and environment: VIRTUAL_HOST options within the file. Also, the networks option at the bottom is necessary to allow this container to “speak” with nginx-proxy.

Also, take note of the line that begins with db_node_domain. If you are going to host multiple WordPress blogs using this method, you need to create unique database names for each. You should replace db_node_domain with your preferring naming scheme. You also need to update two other fields with this chosen name. First:

depends_on:

  - db_node_domain

And second:

WORDPRESS_DB_HOST: db_node_domain:3306

Once the configuration file is set up, you can run the docker-compose up -d command. As long as your DNS is set up to route traffic properly, things should work correctly.

From here, you can start up any number of additional WordPress site—or any type of service, for that matter—and have them be automatically added to the nginx-proxy network. This Docker and Nginx configuration is pretty infinitely extensible, limited only by the VPS resources available to you.

Additional resources to host multiple websites

Of course, be sure to check out the extensive documentation for nginx-proxy to learn more about how you can configure some more complex proxies between Docker and Nginx, such as those using SSL, with multiple ports, or multiple networks.

We haven’t tested it out yet, but there’s a “companion” to nginx-proxy called letsencrypt-nginx-proxy-companion that allows for the creation/renewal of Let’s Encrypt certificates automatically directly alongside the proxy itself.

Just another example of the really cool things that you can do with Docker!

Thanks much to our sharp-eyed reader John! He pointed out how we can improve our docker-compose files by creating unique database names for each instance of WordPress!