Docker Lesson 23 – Docker Compose Introduction | Dataplexa
Section III · Lesson 23

Docker Compose Introduction

You've been running containers one by one — typing docker run five times, each with a dozen flags, then remembering the right order to start them in. There is a better way. Docker Compose lets you define your entire application stack in a single file and start everything with one command.

Every serious Docker project uses Compose. It's not an advanced tool — it's the standard way to run multi-container applications in development and increasingly in production. Once you understand what it does, you'll never want to go back to managing containers manually.

The Problem Compose Solves

Imagine you're starting a typical web application locally — an API, a database, a cache, and a reverse proxy. Without Compose, your startup sequence looks like this:

docker network create app-network
docker volume create postgres-data
docker run -d --name db --network app-network -v postgres-data:/var/lib/postgresql/data -e POSTGRES_PASSWORD=secret postgres:15-alpine
docker run -d --name redis --network app-network redis:7-alpine
docker run -d --name api --network app-network -p 3000:3000 -e DATABASE_URL=postgresql://postgres:secret@db:5432/app -e REDIS_URL=redis://redis:6379 order-api:v1.0.0
docker run -d --name nginx --network app-network -p 80:80 -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro nginx:alpine

Six commands, dozens of flags, specific startup order, easy to forget something, impossible to share with the team reliably. Now imagine a new developer joining — they have to run all of this correctly just to start the app locally.

With Compose, all of that becomes:

docker compose up

The Orchestra Conductor Analogy

Docker Compose is like a conductor leading an orchestra. Each instrument is a container — the API, the database, the cache, the proxy. Without a conductor, every musician starts whenever they feel ready, plays at their own tempo, and the result is chaos. The conductor — Compose — reads the score (your docker-compose.yml), tells each instrument when to start, what to play, how loudly, and how to coordinate with the others. One baton raise. The whole orchestra plays.

The docker-compose.yml File

Everything Compose knows about your application lives in a file called docker-compose.yml in your project root. It's written in YAML — a human-readable format that uses indentation to define structure. The file describes your services, their images, ports, volumes, networks, and environment variables.

The scenario: You're a full-stack developer at an e-commerce startup. Your application has four components — a Node.js API, a PostgreSQL database, a Redis cache, and an nginx reverse proxy. You need every new team member to be able to start the full stack locally in one command, with the correct networking and volumes already set up.

services:                          # top-level key — defines all containers in the stack

  db:                              # service name — becomes the DNS hostname on the network
    image: postgres:15-alpine      # the Docker image to use
    environment:                   # environment variables passed to the container
      POSTGRES_PASSWORD: secret123
      POSTGRES_DB: orders
    volumes:
      - postgres-data:/var/lib/postgresql/data  # named volume for data persistence
    restart: unless-stopped        # restart policy — survives crashes and reboots

  redis:
    image: redis:7-alpine
    restart: unless-stopped

  api:
    build: .                       # build from the Dockerfile in the current directory
    ports:
      - "3000:3000"                # host:container port mapping
    environment:
      NODE_ENV: production
      DATABASE_URL: postgresql://postgres:secret123@db:5432/orders
      REDIS_URL: redis://redis:6379
    depends_on:                    # start db and redis before api
      - db
      - redis
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro   # bind mount for config injection
    depends_on:
      - api
    restart: unless-stopped

volumes:                           # declare named volumes used by services
  postgres-data:                   # Docker creates this volume automatically
[+] Running 5/5
 ✔ Network order-api_default    Created                                  0.1s
 ✔ Volume "order-api_postgres-data"  Created                            0.0s
 ✔ Container order-api-db-1     Started                                  0.8s
 ✔ Container order-api-redis-1  Started                                  0.9s
 ✔ Container order-api-api-1    Started                                  1.2s
 ✔ Container order-api-nginx-1  Started                                  1.4s

What just happened?

One command started your entire application stack. Compose automatically created a custom bridge network (order-api_default) and attached all four containers to it — so they can reach each other by service name immediately. It created the named volume for PostgreSQL. It respected depends_on and started services in the right order — database and Redis first, then API, then nginx. The service names (db, redis, api, nginx) are the DNS hostnames on the network — which is why DATABASE_URL uses @db:5432 instead of an IP address.

The Core Compose Commands

docker compose up           # start all services (foreground — logs stream to terminal)
docker compose up -d        # start all services detached (background)
docker compose up --build   # rebuild images before starting (picks up Dockerfile changes)

docker compose down         # stop and remove containers and the network
docker compose down -v      # also remove named volumes — WARNING: deletes data

docker compose ps           # list running services in the stack
docker compose logs         # show logs from all services
docker compose logs -f api  # follow logs from a specific service in real time

docker compose restart api  # restart a single service without touching the others
docker compose stop         # stop all services without removing containers
docker compose start        # start stopped services
# docker compose ps output
NAME                  IMAGE               STATUS          PORTS
order-api-db-1        postgres:15-alpine  Up 3 minutes
order-api-redis-1     redis:7-alpine      Up 3 minutes
order-api-api-1       order-api           Up 3 minutes    0.0.0.0:3000->3000/tcp
order-api-nginx-1     nginx:alpine        Up 3 minutes    0.0.0.0:80->80/tcp

# docker compose logs -f api (last few lines)
order-api-api-1  | Payment API v1.0.0 starting...
order-api-api-1  | Environment: production
order-api-api-1  | Connected to database at db:5432
order-api-api-1  | Connected to Redis at redis:6379
order-api-api-1  | Server listening on port 3000

What just happened?

docker compose ps shows only the services in the current project — not every container on the machine like docker ps does. The container names follow the pattern projectname-servicename-number. The logs confirm everything connected correctly — the API reached both the database at db:5432 and Redis at redis:6379 using the service names as hostnames. This is Docker's built-in DNS at work, automatically configured by Compose when it created the default network.

docker compose down -v Deletes Your Data

docker compose down stops and removes containers and the network — but leaves your named volumes intact. Adding -v also removes the named volumes — permanently deleting all database data, uploaded files, and any other persisted state. Use down freely during development. Use down -v only when you deliberately want to wipe the slate clean.

Teacher's Note

docker compose up --build is the command I run after any Dockerfile change — it rebuilds images before starting, so you're always running the latest code. Without --build, Compose uses whatever image was last built, even if the Dockerfile changed.

Practice Questions

1. The file that defines all services, networks, and volumes in a Docker Compose application is called what?



2. The Compose key that tells Docker to start one service before another is called what?



3. The command that stops all Compose services and removes their containers and network — but leaves named volumes intact — is called what?



Quiz

1. In a docker-compose.yml, the api service uses the hostname db to connect to the database service. No network configuration is written in the file. How does this work?


2. After changing a Dockerfile, which command ensures Compose rebuilds the image before starting the services?


3. A developer runs docker compose down -v at the end of the day thinking it just stops the containers. The next morning the database is empty. Why?


Up Next · Lesson 24

Docker Compose YAML Deep Dive

You've written your first Compose file — now let's go deep on every key the YAML supports, the patterns professionals use, and how to structure a Compose file that scales with your project.