Docker Compose

Docker · Practical · 15 min

Compose turns a page of docker run commands into one versioned file that the whole team runs the same way.

Real apps are several containers: API, database, cache, worker. Docker Compose describes them declaratively in compose.yaml and manages them as one project with the docker compose command (Compose v2, built into the Docker CLI).

The file's top-level keys:

  • services: each service is a container definition, with image or build, ports, environment, env_file, volumes, depends_on, command and so on, mirroring docker run flags.
  • volumes: named volumes the services use.
  • networks: optional; by default Compose creates one network for the project, and every service can reach every other by service name.

The old top-level version: key is obsolete and ignored.

Core commands, run in the folder with the file:

  • docker compose up -d: build or pull as needed, create the network and volumes, start everything in the background. Run it again after editing the file and only changed services are recreated.
  • docker compose ps, logs -f api, exec db psql -U postgres.
  • docker compose up -d --build: rebuild images first.
  • docker compose down: stop and remove containers and the network. Volumes are kept unless you add -v.

Compose substitutes variables like ${TAG:-latest} from your shell or from a .env file next to compose.yaml. That .env is for interpolating the YAML; env_file: passes variables into containers. They are different mechanisms.

depends_on controls start order only; by default it doesn't wait for the database to be ready. Combine it with healthchecks (condition: service_healthy), covered later, or make the app retry its connection.

Example

# compose.yaml
services:
  api:
    build: .
    image: acme/api:${TAG:-dev}
    ports:
      - "127.0.0.1:8000:8000"
    environment:
      DATABASE_URL: postgres://postgres:dev@db:5432/postgres
    depends_on:
      - db
  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: dev
    volumes:
      - pgdata:/var/lib/postgresql/data
volumes:
  pgdata:
# docker compose up -d --build ; docker compose logs -f api ; docker compose down

Remember

  • compose.yaml declares services, volumes and networks for one project
  • Services reach each other by service name on the project network
  • up -d is idempotent; down keeps volumes unless -v
  • .env interpolates the YAML; env_file passes variables into containers

Watch out for

  • Assuming depends_on waits until the database accepts connections — it only orders startup unless you use a health condition.

Check yourself

  1. How does the api service connect to the db service in the same compose project?
  2. What does docker compose down do to named volumes by default?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.docker.com/compose/