Skip to content
📝 After completing this theory chapter, you should be able to:
  • Explain what the Operate step in the DevOps lifecycle is
  • Describe what IaC is and how it supports the DevOps way of working
  • Explain what Docker Compose is and how it works using different commands
  • Describe why Docker Compose is Considered IaC
  • Explain a given Docker Compose file
  • Create a Docker Compose file when given the needed image, ports, environment variables, network ...

Infrastructure as Code (IaC) with Docker Compose and the Operate step

In the DevOps lifecycle, the "Operate" step involves managing and maintaining the applications and infrastructure that have been developed and deployed. This step focuses on ensuring that the system runs smoothly, efficiently, and reliably in production environments.

The DevOps lifecycle, where we now talk about the Operate step

Infrastructure as Code (IaC) plays a crucial role in the "Operate" step. This is the practice of managing and provisioning computing infrastructure through machine-readable definition files, rather than through manual processes or interactive configuration tools. In essence, it's about defining and managing infrastructure using code. These files usually remain readable for humans as well, lessening the need for documentation.

IaC enables consistent and reliable infrastructure deployment and management. Changes to infrastructure can be tracked, reviewed, and applied in a controlled manner, reducing the risk of configuration drift and human error.

In this way IaC supports the DevOps way of working as well, following several terms that we already saw reflected in the definition of DevOps, such as:

  1. Automation: IaC enables quicker automation of infrastructure deployment and configuration instead of working with commands in script files. Allowing teams to provision and scale infrastructure resources dynamically in response to changing needs.

  2. Collaboration: DevOps encourages collaboration and communication between development, operations, and other stakeholders involved in the software delivery process. With IaC, infrastructure configurations are stored in version control systems, enabling collaboration among team members. Changes to infrastructure can be reviewed, tracked, and audited, fostering transparency and accountability.

  3. Scalability: IaC allows infrastructure to be treated as code, making it easier to scale resources up or down based on demand. By defining infrastructure configurations in code, teams can quickly spin up additional resources or modify existing ones to accommodate changes in workload or user demand, improving scalability and agility.

Docker Compose as an Example of IaC

LogoDocker Compose is a tool for defining and running multi-container Docker applications. It uses YAML-formatted configuration files to define the services, networks, and volumes required for a containerized application to run.

It allows developers to describe the entire application stack of multiple containers in a single file, making it easy to share and deploy the environment across different development and deployment environments without needing to run a lot of commands.

When using Docker Compose, you define your application's infrastructure in a docker-compose.yml file. This file serves as the blueprint for your application's environment, specifying details you already know from Docker such as:

  • Services: Each service represents a containerized application component, such as a web server, a database, or any other small running component.
  • Networks: Defines how the containers communicate with each other, allowing for isolation and segmentation of traffic.
  • Volumes: Specifies where data should be stored persistently, ensuring that data is retained even if containers are destroyed and recreated.

🛠 First Docker Compose example: MySQL Database Container

In a previous chapter we learned about Docker commands to run Docker containers. Each of these commands can be put in the form of a Docker Compose file instead. On top of that many commands can be combined into one file to jointly deploy containers together.

We can start with a Docker Compose configuration that defines the setup for a MySQL database container like we did in a previous chapter:

bash
docker run -d --name mydb2 -p 3307:3306 -e MYSQL_ROOT_PASSWORD=abc123 mysql:latest

As a Docker Compose docker-compose.yml file this would then become:

yaml
services:
  mydb2:
    image: mysql:latest
    container_name: mydb2
    ports:
      - "3307:3306"
    environment:
      MYSQL_ROOT_PASSWORD: abc123
  • services:: This section defines the services to be run as part of the Docker Compose setup.
  • mydb2:: This is the name of the service, which corresponds to the name of the container.
    • image: mysql:latest: Specifies the Docker image to use for the MySQL database service. It pulls the latest version of the official MySQL image from Docker Hub.
    • container_name: mydb2: Sets the name of the container to mydb2.
    • ports:: Maps port 3306 of the container to port 3307 of the host machine, allowing external access to the MySQL database service.
    • environment:: Sets environment variables required by the MySQL image. In this case, it sets the root password for MySQL to abc123.

To run this file using Docker Compose:

  1. Save the Docker Compose configuration above to a file named docker-compose.yml.
  2. Open a terminal and navigate to the directory containing the docker-compose.yml file.
  3. Run the following command to start the containers:
    bash
    docker compose up -d
    The -d flag runs the containers in detached mode, meaning they run in the background.
The result in Docker desktop of placing the docker-compose.yml file in a folder called "Nieuwe map" and running it

Now you have a MySQL database container running with the specified configuration, but perhaps with a project name you don't want. A deployed Docker Compose file is referred to as a project.

🛠 To change that, look up and use the following commands:

  • The Docker compose command to delete the deployment you just made
  • The Docker compose command to deploy a file with your own desired project name instead of the folder name

You should then get a result like this:

The result of deleting the previous deployment and redeploying it with a custom project name

Now also look up how to view all the deployed components within that Docker Compose deployment using command line.

The result of viewing the deployed components of the previously deployed Docker Compose file

Why Docker Compose is Considered IaC?

Docker Compose can be considered Infrastructure as Code because it enables:

Declarative Configuration 💬:

Think of your Docker Compose file like a recipe for your application. Instead of listing step-by-step instructions for how to create each component of your app, you simply declare what you want each part to look like once it's up and running.

For example, you might say "I want a web server using Nginx, a database using MySQL, and I want them to talk to each other." You don't need to tell Docker exactly how to set up Nginx or MySQL with commands; you just declare what services you need and Docker figures out the rest based on the image you specify. That is why this is called Declarative configuration.

Version Control and Collaboration 🔄:

Imagine you're working on a group project where everyone needs to know how the app's infrastructure is set up. With Docker Compose files, you can store this important information in a file that lives alongside your code, and you can keep track of changes using version control tools like Git.

This means that everyone on your team can see what changes have been made, who made them, and when they were made. It also makes it easy to roll back to a previous version if something goes wrong.

Automation and Reproducibility 🤖:

When you use Docker Compose, you can automate the process of setting up your infrastructure. Instead of manually deploying each component with a command, you can use a single Docker Compose file.

This not only saves time and effort, but it also ensures that your infrastructure is set up consistently every time. Whether you're deploying to your development environment, testing environment, or production environment, you can be confident that everything is configured correctly.

Scalability and Flexibility 🚀:

One of the great things about Docker Compose is that it makes it easy to scale your application up or down as needed. If your app suddenly gets a lot of traffic and you need more resources, you can simply increase the number of container instances with a single command.

Likewise, if you need to make changes to your infrastructure, you can update your Docker Compose file and redeploy your app just as easily. This flexibility allows you to iterate quickly and deploy changes without disrupting your users' experience.

Try the following version of our earlier example by editing your previous docker-compose.yml file to have three identical instances of our mysql container:

yaml
services:
  mydb1:
    image: mysql:latest
    container_name: mydb1
    ports:
      - "3307:3306"
    environment:
      MYSQL_ROOT_PASSWORD: abc123

  mydb2:
    image: mysql:latest
    container_name: mydb2
    ports:
      - "3308:3306"
    environment:
      MYSQL_ROOT_PASSWORD: abc123

  mydb3:
    image: mysql:latest
    container_name: mydb3
    ports:
      - "3309:3306"
    environment:
      MYSQL_ROOT_PASSWORD: abc123

And rerun the command you used before to deploy the Docker Compose file with a project name. You'll notice that you actually don't need to delete the old deployment before redeploying the new version.

The --remove-orphans flag

The --remove-orphans flag is an option for the docker compose up command. It's used to remove containers for services that are no longer defined in the Docker Compose configuration.

bash
docker compose up --remove-orphans

When you have a Docker Compose project with multiple services defined in the docker-compose.yml file, and you run docker-compose up to start those services, Docker Compose ensures that all services defined in the configuration are running.

However, if you previously had containers running for services that are no longer defined in the configuration, Docker Compose doesn't automatically remove those containers. They become "orphans" because they're no longer associated with a service definition. This often happens when you edit the docker-compose.yml file and then try to remove it via the Docker Desktop GUI. In that case you can use --remove-orphans

In this updated file, three MySQL services (mydb1, mydb2, and mydb3) are defined, each with its own container name and port mapping. Each service will create a separate instance of the MySQL database container. We also adjust the port mappings as needed to avoid conflicts.

🛠 Dockerfiles in Docker Compose

We learned about the use of Dockerfiles to create our own custom images. This can also be used together with Docker Compose. Instead of having to build our image before we initiate the Docker Compose script, we can let Compose build the image for us.

Try the example to see Docker Compose create a container from a Dockerfile. Put the Dockerfile in the same directory as the docker-compose.yml file.

Dockerfile
FROM python:latest
RUN pip install dinosay
CMD ["dinosay", "-r", "Hello!"]

To let Docker Compose know it needs to build a new image from a Dockerfile, we use the build section:

yaml
services:
  my-container:
    build: .

Run the command docker compose up to start up the new container.

🛠 Editing a Dockerfile

Let's edit our Dockerfile to say something else:

Dockerfile
FROM python:latest
RUN pip install dinosay
CMD ["dinosay", "-r", "We can edit our Dockerfile!"]

Let's try building a container based on the new Dockerfile with docker compose up. The dino still says "Hello!".

When we use the command docker compose up, Docker Compose looks for the changes between the running configuration and the configuration in the docker-compose.yml file. If no changes happened in the file, no changes will be applied to the running containers.

Since no changes happened according to Docker Compose, the old image was used to run the container.

To use the new Dockerfile, we have to use the command docker compose build. This will look at all build sections of the docker-compose.yml file and create new images for them.

Try out docker compose up again and see what happens.

Containers working together

Containers can communicate with eachother. In order to manage which containers can connect, we can create a network so the containers can find eachother. You can create a network with the following command:

bash
docker network create -d my-network

With this command, all containers in "my-network" can now communicate with eachother using the container IP-address or the container name.

You can add containers with the --network flag:

bash
docker run --network my-network ubuntu

The docker0 network

When you install the Docker engine for the first time, Docker will automatically create a network called docker0. This network makes sure that each container gets an IP-address and subnet so it can communicate with the outside world.

This network does not allow containers to communicate with eachother.

We can also create out custom network in Docker Compose. To do this, we can use the following code:

yaml
network:
  my-network:
    driver: bridge

This code will create a container network to which we can add containers. The driver of this network is bridge. This means that containers part of this network can communicate with other containers that are running on the same host. If you don't specify a driver, bridge will be the default driver.

π