Home/Learn/Docker/Docker Compose — Multi-Container Apps

Docker Compose — Multi-Container Apps

Intermediate
Orchestration

Docker Compose defines and runs multi-container applications with a single YAML file — replacing dozens of docker run commands with `docker compose up`.

Overview

Real applications are rarely a single container. A typical web app needs an app server, a database, a cache, and maybe a queue — each in its own container. Running each with docker run, manually linking networks and volumes, is error-prone and not reproducible. Docker Compose solves this with a declarative docker-compose.yml: define all services, networks, and volumes in one file. `docker compose up` builds images, creates the network, starts all services in dependency order, and streams logs. `docker compose down` tears everything down. Compose is also the standard tool for local development environments.

Full Stack docker-compose.yml

A realistic Compose file for a Next.js app with PostgreSQL and Redis. Services on the same Compose file automatically join a shared network and can reach each other by service name.

docker-compose.yml — full-stack app
# docker-compose.yml

version: '3.9'



services:

  # ── Database ────────────────────────────────────────

  postgres:

    image: postgres:16-alpine

    environment:

      POSTGRES_DB: myapp

      POSTGRES_USER: user

      POSTGRES_PASSWORD: secret

    volumes:

      - postgres-data:/var/lib/postgresql/data

    healthcheck:

      test: ["CMD-SHELL", "pg_isready -U user -d myapp"]

      interval: 10s

      timeout: 5s

      retries: 5



  # ── Cache ────────────────────────────────────────────

  redis:

    image: redis:7-alpine

    command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru

    volumes:

      - redis-data:/data



  # ── API Backend ──────────────────────────────────────

  api:

    build:

      context: ./backend

      dockerfile: Dockerfile

    environment:

      DATABASE_URL: postgres://user:secret@postgres:5432/myapp

      REDIS_URL: redis://redis:6379

    ports:

      - "8000:8000"

    depends_on:

      postgres:

        condition: service_healthy   # wait for DB healthcheck to pass

      redis:

        condition: service_started

    restart: unless-stopped



  # ── Frontend ─────────────────────────────────────────

  frontend:

    build: ./frontend

    environment:

      NEXT_PUBLIC_API_URL: http://localhost:8000

    ports:

      - "3000:3000"

    depends_on:

      - api



volumes:

  postgres-data:

  redis-data:

Essential Compose Commands

Compose commands map naturally to docker commands but operate on all services at once. The new `docker compose` (v2) is integrated into the Docker CLI.

bash — docker compose workflow commands
# Start all services (build if needed), stream logs

docker compose up



# Start in background

docker compose up -d



# Rebuild images then start

docker compose up --build



# Start only specific services

docker compose up -d postgres redis



# View logs

docker compose logs -f api          # follow logs for api service

docker compose logs --tail=50       # last 50 lines from all services



# Run a one-off command in a service container

docker compose exec api sh          # open shell

docker compose exec postgres psql -U user -d myapp



# Scale a service (run multiple replicas)

docker compose up --scale api=3 -d



# Stop and remove containers, networks (NOT volumes)

docker compose down



# Stop and remove everything including volumes (⚠️ deletes DB data!)

docker compose down -v

Environment Files & Overrides

Use .env files for environment-specific values and docker-compose.override.yml for dev vs prod differences without duplicating the base file.

Compose override files — dev vs prod
# .env (auto-loaded by Compose — do not commit to git!)

POSTGRES_PASSWORD=mysecretpassword

API_PORT=8000



# docker-compose.yml (base — production values)

services:

  api:

    image: myregistry/api:${IMAGE_TAG}

    environment:

      DATABASE_URL: postgres://user:${POSTGRES_PASSWORD}@postgres:5432/myapp



# docker-compose.override.yml (auto-merged in dev — git-committed)

# Only differences from base config needed here

services:

  api:

    build: ./backend        # build locally in dev, use image in prod

    volumes:

      - ./backend/src:/app/src   # hot-reload source code

    environment:

      LOG_LEVEL: debug



# docker-compose.prod.yml (explicit prod overrides)

# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Key Points to Remember

  • 1Compose defines multi-container apps declaratively in a single YAML file.
  • 2All services in a Compose file share a default network and resolve each other by service name.
  • 3depends_on with condition: service_healthy waits for healthchecks before starting dependent services.
  • 4docker-compose.override.yml is auto-merged and is the standard pattern for dev overrides.
  • 5Use .env files for secrets and environment-specific values — never hardcode passwords.
  • 6docker compose down -v deletes volumes — use with caution, it wipes database data.

Interview Questions

Sign in to ask Aria
1

What does depends_on do in Docker Compose?

2

How do you handle environment-specific configuration in Compose?

Ask Aria about Docker Compose — Multi-Container Apps

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…