Skip to content
6 min read·Lesson 9 of 10

Multi-Container Apps with Docker Compose

Define an entire application stack — web server, API, database, queue — in a single docker-compose.yml file and bring it up with one command.

Real applications are rarely a single container. A typical stack might include a web frontend, an API, a database, a cache, and a background worker. Running them with individual docker run commands is fragile and tedious. Docker Compose declares the entire stack in one YAML file.

A Realistic Example

Save as compose.yaml (the modern filename; older guides use docker-compose.yml):

services:
  web:
    image: nginx:1.27
    ports:
      - "8080:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - api

  api:
    build: ./api
    environment:
      DATABASE_URL: postgres://app:secret@db:5432/app
      REDIS_URL: redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    restart: unless-stopped

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: app
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 5s
      timeout: 3s
      retries: 5

  cache:
    image: redis:7-alpine

volumes:
  pgdata:

Bring it up:

docker compose up -d           # start everything in the background
docker compose ps              # see what's running
docker compose logs -f api     # tail one service's logs
docker compose exec api sh     # shell into the api container
docker compose down            # stop and remove containers and networks
docker compose down -v         # also remove named volumes (destructive!)

Anatomy of a Compose File

Top-level keyPurpose
servicesThe containers that make up your app
networksCustom networks (Compose creates a default one if you omit this)
volumesNamed volumes that persist across compose down
configsConfiguration files mounted into services
secretsSecret values mounted as files (more secure than env vars)

Per-service options

KeyPurpose
imagePull this image from a registry
buildBuild from a local Dockerfile
portsPublish ports to the host
environment / env_fileSet environment variables
volumesMount named volumes or bind mounts
depends_onOrder startup; supports health-check conditions
healthcheckHow Compose probes liveness
restartRestart policy (no, on-failure, always, unless-stopped)
command / entrypointOverride the image's defaults
deploy.replicasRun N copies of the service (Swarm; Compose ignores in basic mode)

Networking and Service Discovery

Compose creates a private network shared by all services in the project. Each service is reachable from the others via its service name as a DNS hostname. In the example above, the API connects to Postgres at db:5432 and Redis at cache:6379. No IP juggling.

Profiles for Optional Services

Use profiles to scope services to specific scenarios — a heavy ML container only used during data ingest, an admin tool only needed locally:

services:
  app:
    image: myapp
  pgadmin:
    image: dpage/pgadmin4
    profiles: ["debug"]
docker compose up                       # starts only "app"
docker compose --profile debug up        # starts both

Override Files

Compose automatically merges compose.yaml with compose.override.yaml if present — perfect for local-only tweaks (extra mounts, debug ports). For environment-specific configurations, supply multiple files:

docker compose -f compose.yaml -f compose.prod.yaml up -d

When to Outgrow Compose

Compose is excellent for:

  • Local development of multi-service apps
  • Small single-host deployments (a side project, an internal tool)
  • CI environments that need an integration database/queue/etc.

Compose is not built for:

  • Multi-node clusters with auto-failover (use Kubernetes)
  • Rolling updates with health gating
  • Service meshes, advanced ingress, secrets rotation

The good news: a Compose file translates almost line-for-line to a Kubernetes manifest. Tools like Kompose automate the conversion, and the mental model — services, networks, volumes — carries over directly.

Key Takeaways

  • Docker Compose lets you declare a multi-service stack in YAML and manage it as a unit.
  • A compose file defines services, networks, volumes, and dependencies between them.
  • docker compose up starts everything; docker compose down stops and cleans up.
  • Services on the same compose project automatically share a network and resolve each other by service name.
  • Compose is ideal for local development and small single-host deployments — Kubernetes takes over for production at scale.

Test your knowledge

Try exam-style practice questions to reinforce what you've learned.

Practice Questions →