Skip to content

1.07 Docker Compose

Overview

Docker Compose is a tool for defining and running multi-container Docker applications using a single YAML configuration file.

Abstract

Instead of running multiple docker run commands manually and wiring containers together with flags, Docker Compose lets you declare the entire application stack β€” services, ports, networks, volumes, and dependencies β€” in one docker-compose.yml file. A single docker-compose up command brings everything up. This is the standard approach for local development and single-host deployments.


Why It Matters in Production

Real applications consist of multiple services: frontends, backends, databases, caches, workers. Managing these with raw docker run commands is error-prone and hard to reproduce. Docker Compose provides:

  • Reproducibility β€” the entire stack is defined as code
  • Dependency ordering β€” control which containers start first
  • Isolated networking β€” services communicate by name without manual linking
  • Single command operations β€” up, down, logs, ps work across all services

Key Concepts

Concept Description
docker-compose.yml YAML file that defines all services, networks, and volumes
Service A container definition β€” image, ports, environment, networks
docker-compose up Build (if needed) and start all services
docker-compose down Stop and remove containers, networks
links Deprecated way to connect containers; replaced by named networks
depends_on Controls startup order between services (v2+)
build Tells Compose to build the image from a Dockerfile instead of pulling
Networks Isolated communication layers; Compose auto-creates one per project (v2+)

Sample Application β€” Voting App

A multi-service stack used throughout this course. It demonstrates how different technologies are wired together:

voting-app (Python) β†’ redis (in-memory DB) β†’ worker (.NET)
                                                     ↓
result-app (Node.js) ←————————————— db (PostgreSQL)
Service Technology Role
voting-app Python Web UI; user casts vote; writes to Redis
redis Redis In-memory store for incoming votes
worker .NET Reads from Redis; writes to PostgreSQL
db PostgreSQL Persistent vote count storage
result-app Node.js Web UI; reads from PostgreSQL; shows results

Example Configuration or Commands

Running Manually with docker run

docker run -d --name=redis redis
docker run -d --name=db postgres:9.4
docker run -d --name=vote -p 5000:80 voting-app
docker run -d --name=result -p 5001:80 result-app
docker run -d --name=worker worker

Containers are isolated β€” they cannot find each other without explicit linking.

redis:
  image: redis

db:
  image: postgres:9.4

vote:
  image: voting-app
  ports:
    - 5000:80
  links:
    - redis

result:
  image: result-app
  ports:
    - 5001:80
  links:
    - db

worker:
  image: worker
  links:
    - redis
    - db

Links are Deprecated

links manually injects /etc/hosts entries into containers. This feature is deprecated. Use named networks (v2+) instead.

Version 2 β€” Services Section + depends_on

version: "2"

services:
  redis:
    image: redis

  db:
    image: postgres:9.4

  vote:
    image: voting-app
    ports:
      - 5000:80
    depends_on:
      - redis

  result:
    image: result-app
    ports:
      - 5001:80

  worker:
    image: worker
  • All services join an auto-created bridge network
  • Services reference each other by service name (no links needed)
  • depends_on controls startup order

Version 3 β€” With Environment Variables

version: "3"

services:
  redis:
    image: redis

  db:
    image: postgres:9.4
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres

  vote:
    image: voting-app
    ports:
      - 5000:80

  worker:
    image: worker-app

  result:
    image: result-app
    ports:
      - 5001:80

Version 3 adds Docker Swarm compatibility and removes some v2-only options.

Building Images with Compose

Replace image: with build: to build from a local Dockerfile:

vote:
  build: ./vote          # path to folder containing Dockerfile
  ports:
    - 5000:80
  links:
    - redis

result:
  build: ./result
  ports:
    - 5001:80

worker:
  build: ./worker

On docker-compose up, Compose builds the image first, then starts the container.

Networks β€” Frontend / Backend Separation

version: "2"

services:
  redis:
    image: redis
    networks:
      - back-end

  db:
    image: postgres:9.4
    networks:
      - back-end

  vote:
    image: voting-app
    networks:
      - front-end
      - back-end

  result:
    image: result
    networks:
      - front-end
      - back-end

  worker:
    image: worker
    networks:
      - back-end

networks:
  front-end:
  back-end:

Version Comparison

Feature Version 1 Version 2 Version 3
version: key required No Yes Yes
services: wrapper No Yes Yes
Auto bridge network No (default) Yes (dedicated) Yes (dedicated)
links needed Yes No No
depends_on No Yes Yes
Docker Swarm support No No Yes
environment variables Yes Yes Yes

Best Practices

Best Practices

  • Always use version 3 for new projects β€” it's Swarm-compatible and feature-complete.
  • Use named networks to segment traffic (e.g. front-end vs back-end).
  • Use depends_on to express startup order, but note it only waits for the container to start β€” not for the service inside to be ready. Use health checks for true readiness.
  • Use build: during development so Compose manages image builds; switch to pre-built images in CI/CD.
  • Name containers explicitly for clarity and easy referencing.
  • Keep docker-compose.yml under version control β€” it is infrastructure as code.

Security Best Practices

Security

  • Never hardcode passwords in docker-compose.yml. Use environment variable files (.env) or Docker Secrets (Swarm mode).
  • Add .env to .gitignore to prevent credential leaks.
  • Avoid exposing database ports (e.g. 5432, 6379) to the host unless strictly necessary.
  • Use internal networks to isolate backend services from direct external access.
  • Use read-only mounts where containers do not need write access to volumes.
  • Pin image versions (e.g. postgres:9.4) β€” avoid latest in production to prevent unexpected upgrades.

Do and Don't

βœ… Do ❌ Don't
Use version 3 with services: block Use version 1 flat format for new projects
Use named networks for traffic isolation Rely on links (deprecated)
Use depends_on for startup ordering Assume depends_on means the service is ready
Use .env files for secrets Hardcode passwords in docker-compose.yml
Pin image tags (postgres:9.4) Use latest in production
Use build: for local development Re-pull pre-built images on every change

Common Mistakes

Common Mistakes

  • Missing links in v1: Forgetting links: in version 1 causes containers to fail to resolve service hostnames.
  • Version mismatch: Using v1 syntax (no services: key) with a version: "2" header causes parse errors.
  • Port conflicts: Publishing the same host port twice (e.g. two services on 5000) will fail on up.
  • depends_on is not a health check: The dependent container starts but the service inside (e.g. Postgres) may not yet be accepting connections.
  • Forgetting the networks: root block: Defining networks under services without declaring them at root level causes errors.

Troubleshooting

# View running services and their status
docker-compose ps

# Follow logs for all services
docker-compose logs -f

# Follow logs for a single service
docker-compose logs -f vote

# Restart a single service
docker-compose restart worker

# Rebuild images and recreate containers
docker-compose up --build

# Tear down all containers and networks
docker-compose down

# Tear down including volumes
docker-compose down -v

# Validate the compose file syntax
docker-compose config

Quick Recap

  • Docker Compose manages multi-container applications via a single YAML file
  • docker-compose up starts everything; docker-compose down stops and cleans up
  • Version 1 uses flat format + links; Version 2 introduces services:, auto-networking, and depends_on; Version 3 adds Swarm support
  • In v2+, containers discover each other by service name β€” no links required
  • Use build: to build from local Dockerfiles instead of pulling images
  • Use separate networks to isolate front-end and back-end traffic
  • Secrets belong in .env files or Docker Secrets β€” never inline in the YAML

Interview / Revision Notes

Revision

Q: What problem does Docker Compose solve? Managing multi-container apps as a single unit instead of running and wiring individual docker run commands.

Q: How do containers communicate in Compose v2+? Compose creates a dedicated bridge network. Services resolve each other by service name automatically β€” no links needed.

Q: What does depends_on do? It controls start order β€” the specified service starts before the dependent one. It does not wait for the service to be ready (health checks handle that).

Q: What is the difference between image: and build: in Compose? image: pulls a pre-built image from a registry. build: builds an image locally from a Dockerfile at the given path.

Q: Why are links deprecated? Docker's built-in networking (from v2 onwards) handles service discovery by name. Links were a manual workaround that is no longer needed.

Q: How do you separate front-end and back-end traffic in Compose? Define named networks (front-end, back-end) in a root networks: block and assign each service to the appropriate network(s).