What is YAML and a Compose File

So, you’ve heard about Docker and the magic of containers, but the words “YAML” and “Compose file” sound like something out of a sci-fi movie? Don’t worry. You are in the right place. If you’re new to Docker and wondering how to use a docker-compose.yml file to manage your containers, I’ve got you covered.

When you finish this guide, you’ll know what a Docker Compose file is, how to use it, and why it makes running multiple Docker containers easier.


What Is a Docker Compose File?

At its core, a Docker Compose file is just a simple YAML file (usually named docker-compose.yml) that tells Docker how to run multiple containers together.

Think of it like a recipe. Instead of manually starting each container, setting its configurations, and linking them together one by one, you write it all down in this file, and Docker does the rest.


Why Use Docker Compose?

Docker Compose simplifies container management. Here’s why you should use it:

  • Easy multi-container setup – Instead of running multiple docker run commands, you define everything in one file.  

  • Portability – Share your docker-compose.yml file to allow anyone to replicate your setup.

  • Easier management – Start, stop, or restart all your containers with a single command.  

  • Environment variables – Configure your setup using a simple .env file.

Now that you know why it’s useful, I will break down my docker-compose.yml file later in this post.


What Is Orchestration?

Orchestration is just a fancy word for automating how different parts of your system work together.

Think of it like a movie production. You have multiple moving parts that all need to work together seamlessly:

  • The director ensures everything happens at the right time.

  • The actors perform their roles based on the script.

  • The camera crew captures the right angles.

  • The editors put everything together in post-production.

If each person had to be manually told what to do every single time, it would be chaos. Instead, they follow a predefined plan, and everything runs smoothly without constant micromanagement.

Docker Compose works the same way. Instead of manually starting each container, configuring it, and linking everything together, Docker automates the process so your services interact as expected.


YAML Formatting Rules (Read This or Nothing Will Work!)

Before diving into the actual docker-compose.yml, you must understand YAML formatting because one small mistake in spacing or indentation will break everything. Unlike JSON or XML, YAML relies heavily on proper indentation and formatting.

YAML Rules You Must Follow:

  • Use spaces, not tabs – Indentation must be done using spaces. Tabs are not allowed.

  • Consistent indentation – Always use the same number of spaces per level (2 or 4 spaces are common).

  • Key-value pairs are separated by colons – Example: container_name: radarr.

  • Lists use dashes (-) – Each item in a list starts with -.

  • Strings don’t need quotes (but sometimes they do) – Strings are usually fine without quotes, but use double quotes "" if the string contains special characters.

  • Boolean values (true, false) and numbers don’t need quotes – Just write them as they are.

Example of Correct vs. Incorrect YAML Formatting

Correct YAML

services:
  radarr:
    container_name: radarr
    restart: unless-stopped

Incorrect YAML (Tabs used instead of spaces)

services:
	radarr:  # ❌ Tabs used (invalid)
		container_name: radarr
		restart: unless-stopped

Incorrect YAML (Inconsistent indentation)

services:
  radarr:
    container_name: radarr
       restart: unless-stopped  # ❌ Indentation is off (invalid)

Breaking Down a Docker Compose File

Here’s a real-world example of a docker-compose.yml file that sets up Radarr, Sonarr, Prowlarr, and SABnzbd essential tools for an automated media server. Following this example, you should be able to easily add other services.

services:
  radarr:
    image: lscr.io/linuxserver/radarr:latest
    container_name: radarr
    env_file: .env
    ports:
      - "${RADARR_PORT}:7878"
    volumes:
      - ${CONFIG_PATH}/radarr:/config
      - ${DOWNLOADS_PATH}:/downloads
      - ${MEDIA_PATH}/Movies:/movies
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    restart: unless-stopped
    depends_on:
      - sabnzbd
      - prowlarr

  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    env_file: .env
    ports:
      - "${SONARR_PORT}:8989"
    volumes:
      - ${CONFIG_PATH}/sonarr:/config
      - ${DOWNLOADS_PATH}:/downloads
      - ${MEDIA_PATH}/TV:/tv
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    restart: unless-stopped
    depends_on:
      - sabnzbd
      - prowlarr

  prowlarr:
    image: lscr.io/linuxserver/prowlarr:latest
    container_name: prowlarr
    env_file: .env
    ports:
      - "${PROWLARR_PORT}:9696"
    volumes:
      - ${CONFIG_PATH}/prowlarr:/config
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    restart: unless-stopped

  sabnzbd:
    image: lscr.io/linuxserver/sabnzbd:latest
    container_name: sabnzbd
    env_file: .env
    ports:
      - "${SABNZBD_PORT}:8080"
    volumes:
      - ${CONFIG_PATH}/sabnzbd:/config
      - ${DOWNLOADS_PATH}:/downloads
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    restart: unless-stopped

Now let’s break it down piece by piece.


1. Defining Services

The services: section lists all the containers you want to run. Each service is essentially a separate Docker container.

  • radarr and sonarr are the names of each service.
  • Each service runs a specific Docker image (lscr.io/linuxserver/radarr, etc.).
  • The container_name specifies a custom name for each running container.

2. Environment Variables

Instead of hardcoding values, we use an .env file to store environment variables. This makes it easy to change settings without modifying the Compose file.

Example .env file:

RADARR_PORT=7878
SONARR_PORT=8989
PROWLARR_PORT=9696
SABNZBD_PORT=8080
CONFIG_PATH=/path/to/config
DOWNLOADS_PATH=/path/to/downloads
MEDIA_PATH=/path/to/media
PUID=1000
PGID=1001
TZ=America/Denver

This way, you can just change the .env file without editing the docker-compose.yml every time.

3. Port Mapping

Each container has ports mapped like this:

ports:
  - "${RADARR_PORT}:7878"

This means port 7878 inside the container (where Radarr runs) is accessible on your local machine at ${RADARR_PORT} (set in the .env file).

4. Volume Mounts

Volumes persist data between container restarts. Without them, you’d lose your settings when restarting.

volumes:
  - ${CONFIG_PATH}/radarr:/config
  - ${DOWNLOADS_PATH}:/downloads
  - ${MEDIA_PATH}/Movies:/movies

5. Restart Policy

The restart: unless-stopped line ensures the container will restart automatically unless you manually stop it.

6. Depends On

The depends_on keyword is used to define startup order between containers. It ensures that certain containers start before others, but it does NOT wait until the dependent container is fully ready, only that it has started.

Confused about .env files? See my Docker .env post for a detailed breakdown.


Running the Docker Compose File

Once your docker-compose.yml and .env files are ready, all you have to do is:

1. Navigate to the folder containing your Compose file:

cd /path/to/your/compose/file

2. Start your containers (in the background):

docker compose up -d

-d runs the containers in detached mode, so they keep running in the background.

3. Check running containers:

docker ps

4. Stopping the containers:

docker compose down

This stops and removes all containers but keeps your data intact.


Wrapping It Up

Congratulations. You now know how to use a Docker Compose YAML file to spin up multiple containers with just one command.

This is just the start. Docker Compose can manage networks and even define dependencies between services. But for now, you have a solid foundation.

Give it a try, and soon you’ll wonder how you ever managed Docker without it.