Container Networks with Docker Compose


One of the best things about Docker is that containers have built-in features for connecting with one-another. In this way, if I wanted to create a complicated system that consists of several different components (databases, microservices, etc.), then Docker gives me a convenient way to setup that system very quickly. This is where Docker Compose comes in, an add-on to Docker that lets us create multiple containers at once in a pre-configured manner.

Installing Docker Compose

As mentioned, Docker Compose is an add-on and doesn’t come with Docker by default. You can install it with the following command on Linux (or check out other installation methods):

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

Docker Compose Files

Just with regular containers, Docker lets us use files to define how we want to create our container networks. We have to create compose files for this, which are based on a standard called yaml. Yaml, for simpler terms, can be thought of as JSON but with indentation instead of brackets. You can use tabs or spaces, but need to make sure you stay consistent. Here is a simple example that I hope gets the idea across:

my_object:
  my_values: helloworld
  my_list_of_values:
    - value1
    - value2

The docker compose yaml file needs to follow a certain structure. At the top of the file, we need to define what version of compose we are going to be using. Then, we need to define the services object, which is the list of all the different containers we wish to use in our composition. Finally, we are going to define networks, which make it easier for us to define what services can talk to each other.

Docker Compose Commands

Docker compose also comes with its own set of commands. Here are the two most important ones:

CommandFunctionality
docker-compose upStarts building and setting up containers based on the compose file in the directory specified as the argument. If blank, looks in the current directory.
docker-compose buildBuilds all the containers in the specified directory. If blank, looks for file in current directory.

Creating our first Service

For starters, let’s include the version of compose that we will be using (right now, the latest version is 3):

version: "3"

Next, we want to define our first service. For this example, I’m going to create a simple WordPress setup with a MySQL database. Let’s start with the database, since that can pretty much stand on its own.

version: "3"
services:
  db:
    container_name: mariadb
    image: mariadb:10.4.7
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: mydb
    ports:
      - 3306:3306

If you read my previous tutorial on the Docker command line, a lot of these terms will be quite familiar. This is because what we are doing here is very similar to building a single container and customising it by setting the various available parameters (exposed ports, environment variables, etc.). We can see that we have our services object, and inside we are defining a service that I named “db”. I’m giving it the name “mariadb”, so that when I start my compose I can refer to it using that name (for example using command “docker stop maria” to stop the container). I’m also defining an image for the container, setting the environment variables etc. The only part that we cannot set via the command line is the “restart” attribute. Setting this to “always” means that whenever we use the command to build and start our compose, the maria container that is currently running is stopped and restarted (as opposed to being ignored). Let’s try it! Go into a new folder, copy paste the code into a file named docker-compose.yaml and execute the docker-compose up command. You should see that a new instance of the MariaDB container is started!

Adding a second Service

We just saw how a compose file can be used to create a single container and for some more complex configurations, it can often make sense to use compose files if you are just trying to start one container despite it being designed to start multiple at once. But now you will see how easy it is to start two of them! We will add a simple WordPress configuration. We are also going to look into the networks and how they are useful. First, let’s take a look at the code:

version: "3"
services:
  db:
    container_name: mariadb
    image: mariadb:10.4.7
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: mydb
    ports:
      - 3306:3306
    networks:
      - wpsite
  wordpress:
    depends_on:
      - db
    container_name: wp
    image: wordpress:php7.2
    ports:
      - "9000:80"
    expose:
      - 9000
    restart: always
    volumes: [".:/var/www/html"]
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_NAME: mydb
      WORDPRESS_DB_PASSWORD: password
    networks:
      - wpsite
networks:
  wpsite:

You’ll notice that I added a wordpress service. The first thing I do is indicate that our WordPress server “depends” on the database, this means that our database container will always be built and started first. Besides doing a port mapping, I’m also exposing port 9000 to the host machine, so that we can actually see the WordPress server running. I’m also setting up a volume, which is just going to map the WordPress config files to the same directory that we are executing our compose command from. The environment variables are used to setup the connection with the database. Notice how our WORDPRESS_DB_HOST is using db:3306 as the value, instead of something like 127.0.0.1. This is because containers in the same network (more about this in a bit) know about each other and can be referenced by their service names.

Finally, we now see that both services have a new networks attribute, in which both have a value of “wpsite”. At the bottom of the document, we have another “networks” object and a definition of “wpsite”. Through this, we are telling docker compose that we want to create a network (connection between containers) and allows us to indicate which containers should know about each other. If networks is not defined, then a container is part of the global network and thus can be accessed by any containers – this is generally not a great practice, because it can lead to unwanted side effects for larger compositions.

Now, let’s try this out! Run docker-compose up again and voila! At localhost:9000, we can see that our WordPress server is now running.