Container Networks with Docker Compose
Tags: docker, devops
Published: 2020-03-22 11:39:08

<!-- wp:paragraph -->
<p>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.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>Installing Docker Compose</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>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 (<a href="https://docs.docker.com/compose/install/">or check out other installation methods</a>):</p>
<!-- /wp:paragraph -->

<!-- wp:html -->
<pre><code class="language-bash">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</code></pre>
<!-- /wp:html -->

<!-- wp:heading {"level":3} -->
<h3>Docker Compose Files</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>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:</p>
<!-- /wp:paragraph -->

<!-- wp:html -->
<pre><code class="language-bash">my_object:
  my_values: helloworld
  my_list_of_values:
    - value1
    - value2</code></pre>
<!-- /wp:html -->

<!-- wp:paragraph -->
<p>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.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>Docker Compose Commands</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Docker compose also comes with its own set of commands. Here are the two most important ones:</p>
<!-- /wp:paragraph -->

<!-- wp:table {"className":"table table-striped"} -->
<table class="table table-striped"><thead><tr><th scope="col">Command</th><th scope="col">Functionality</th></tr></thead><tbody><tr></tr><tr><td>docker-compose up</td><td>Starts building and setting up containers based on the compose file in the directory specified as the argument. If blank, looks in the current directory.</td></tr><tr><td>docker-compose build</td><td>Builds all the containers in the specified directory. If blank, looks for file in current directory.</td></tr>
</tbody></table>
<!-- /wp:table -->

<!-- wp:heading {"level":3} -->
<h3>Creating our first Service</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>For starters, let's include the version of compose that we will be using (right now, the latest version is 3):</p>
<!-- /wp:paragraph -->

<!-- wp:html -->
<pre><code class="language-bash">version: "3"</code></pre>
<!-- /wp:html -->

<!-- wp:paragraph -->
<p>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.</p>
<!-- /wp:paragraph -->

<!-- wp:html -->
<pre><code class="language-bash">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</code></pre>
<!-- /wp:html -->

<!-- wp:paragraph -->
<p>If you read my<a href="https://byteschool.io/post/docker-command-line-guide-docker-series-part-3"> previous tutorial</a> 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 <em>docker-compose up</em> command. You should see that a new instance of the MariaDB container is started!</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>Adding a second Service</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>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:</p>
<!-- /wp:paragraph -->

<!-- wp:html -->
<pre><code class="language-bash">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:</code></pre>
<!-- /wp:html -->

<!-- wp:paragraph -->
<p>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. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>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.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Now, let's try this out! Run <em>docker-compose up</em> again and voila! At localhost:9000, we can see that our Wordpress server is now running.</p>
<!-- /wp:paragraph -->