Skip to main content

Docker Compose Basics

Docker Compose is a YAML-based tool to define and run multi-container Docker applications.

Concept explained

At its simplest, a docker-compose.yml lists services. Each service maps to an image or a build context, optional ports, environment variables, volumes and dependencies.

Key pieces:

  • services: named containers that make up the app (web, db, redis).
  • volumes: persistent storage (databases, uploads).
  • networks: how services talk to one another (default network is created automatically).
  • environment / env_file: configure services.
  • depends_on: controls start order but not readiness – use healthchecks if you need service readiness.
tip

Prefer the modern CLI command docker compose (no hyphen) when using Docker Compose v2. Some systems may still support docker-compose.

Step-by-step example

Below is a minimal app with a simple Node web service and Postgres. Put this docker-compose.yml in your project root.

docker-compose.yml:

services:
web:
build: . # builds using ./Dockerfile
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://postgres:password@db:5432/app
depends_on:
- db

db:
image: postgres:15
environment:
POSTGRES_PASSWORD: password
volumes:
- db-data:/var/lib/postgresql/data

volumes:
db-data:

Minimal Dockerfile for the web service (Node example):

FROM node:18-slim
WORKDIR /app
COPY package.json ./
COPY app.js ./
EXPOSE 3000
CMD ["npm", "start"]

Common commands:

  • Build and start in foreground: docker compose up --build
  • Start in detached mode: docker compose up -d
  • Stop and remove: docker compose down
  • View logs: docker compose logs -f web
  • Run a one-off command: docker compose exec web sh

Variations & gotchas

  • Healthchecks: depends_on does not wait for app readiness. Add a healthcheck to the DB and use condition: service_healthy in older compose specs or implement a retry in the app.
  • .env files: Compose supports an .env file; avoid committing secrets. See Environment variables & Secrets for patterns.
  • Bind mounts vs named volumes: bind mounts are handy for local development; use named volumes for persistent production data.
  • Compose file version: modern versions of Docker Compose automatically use the latest schema, so you don't need to specify a version in your compose file.
  • Scaling: docker compose up --scale worker=3 works for stateless services, not for stateful DBs.
caution

Don't rely on Compose as a production orchestrator for large, multi-host clusters. For small apps Compose is often sufficient.

Common mistakes

  • Exposing databases publicly by publishing DB ports (e.g., - "5432:5432"). Avoid unless needed.
  • Forgetting volumes for databases – leads to data loss when containers are removed.
  • Using latest image tags in production – makes rollbacks and reproducibility hard.
  • Expecting depends_on to guarantee the dependent service is ready (it only controls start order).
  • Committing .env or credentials to version control.

Best practices

  • Pin image versions (e.g., postgres:15) for reproducible runs.
  • Use named volumes for stateful services and bind mounts for local dev.
  • Keep small services and one responsibility per container.
  • Add a sensible restart policy (e.g., unless-stopped).
  • Use healthchecks for services that other services depend on.
  • Put secrets in a secret manager or use environment files kept out of VCS.

When to use / when not to use

When to use:

  • Local development, CI tasks, small staging or production apps.
  • Single-host deployment or simple multi-container stacks.
  • Fast iteration workflows where Docker Compose reduces infra overhead.

When not to use:

  • Large, multi-node clusters requiring service discovery, autoscaling, or advanced scheduling – use orchestration (e.g., Kubernetes).
  • When you need multi-region deployments and complex network policies.

Key takeaways

  • Docker Compose ties services, networks and volumes in a single file so you can run multi-container apps with one command.
  • Use named volumes for persistent data, pin image versions, and add healthchecks for readiness.
  • docker compose up --build starts the app; docker compose down removes it – remember volumes persist unless removed explicitly.
  • Compose is great for local and small-scale deployments but not a drop-in replacement for full orchestrators.