Docker Compose

Define and run multi-container applications with Docker Compose v2: services, networks, volumes, healthchecks, depends_on, and profiles.

Docker Compose lets you describe a multi-container application in a single YAML file and manage it with one command. Instead of typing long docker run commands for the app, database, and cache, you declare services, networks, and volumes once and bring everything up consistently. It is the standard tool for local development, integration testing, and small single-host deployments.

The Compose File

Modern Compose (v2, invoked as docker compose) follows the Compose Specification. The old top-level version: key is obsolete and can be omitted. The default file name is compose.yaml (docker-compose.yml is still supported).

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      DB_HOST: db
      DB_USER: app
      DB_PASSWORD: ${DB_PASSWORD}
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

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

  cache:
    image: redis:7
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redisdata:/data

volumes:
  pgdata:
  redisdata:

Put the password in a .env file next to the Compose file (and keep that file out of Git):

DB_PASSWORD=change-me

Compose reads .env automatically for variable substitution.

Everyday Commands

docker compose up -d            # create and start everything in the background
docker compose up -d --build    # rebuild images first
docker compose ps               # list project containers
docker compose logs -f app      # follow one service's logs
docker compose exec db psql -U app appdb   # run a command in a running service
docker compose run --rm app sh  # one-off container for a service
docker compose stop             # stop containers, keep them
docker compose down             # stop and remove containers and networks
docker compose down -v          # also remove named volumes (data loss)
docker compose config           # validate and print the resolved configuration

Key Service Options

Option Purpose
image Image to run
build Build from a Dockerfile (context, dockerfile, args, target)
ports Publish ports as "host:container"
environment / env_file Set environment variables
volumes Mount named volumes or bind mounts
networks Attach to specific networks
depends_on Control start order, optionally waiting for health
healthcheck Define how readiness is tested
restart Restart policy (no, always, on-failure, unless-stopped)
command / entrypoint Override image defaults
profiles Start a service only when its profile is enabled
deploy.resources Resource limits and reservations

depends_on and Readiness

Plain depends_on: [db] only controls start order; it does not wait until the database accepts connections. Combine it with a healthcheck and condition: service_healthy as in the example above so the app starts only after the database is ready. Applications should still retry connections, because dependencies can restart later.

Networking and Service Discovery

Compose creates one default network per project, and each service is reachable by its service name. In the example, the app connects to db:5432 and cache:6379. Ports under ports: are only needed for access from the host.

Development Overrides and Profiles

Compose automatically merges compose.yaml with compose.override.yaml, which is handy for development settings:

# compose.override.yaml
services:
  app:
    volumes:
      - ./src:/app/src
    environment:
      LOG_LEVEL: debug

Use multiple files explicitly for other environments:

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

Profiles keep optional services out of the default start:

services:
  adminer:
    image: adminer
    ports: ["8081:8080"]
    profiles: ["debug"]
docker compose --profile debug up -d

Common Mistakes

  • Assuming depends_on waits for readiness. Without condition: service_healthy the app may start before the database is usable.
  • Committing secrets in the Compose file. Use .env (ignored by Git), env_file, or Compose secrets.
  • Running docker compose down -v casually. It deletes named volumes and your data.
  • Using localhost to reach another service. Use the service name.
  • Mixing up docker-compose and docker compose. The hyphenated v1 tool is deprecated; use the docker compose plugin.

Best Practices

  • Pin image tags and keep Compose files in version control.
  • Add healthchecks to services others depend on.
  • Use named volumes for data and a .env file or secrets for credentials.
  • Use override files and profiles to separate development, test, and production concerns.
  • Run docker compose config to check the final merged configuration before deploying.

Interview Questions

1. What is Docker Compose and why use it?

Compose defines multi-container applications in a YAML file and manages their lifecycle with commands like docker compose up and down. It makes environments reproducible and removes the need for long, error-prone docker run commands.

2. Does depends_on wait for a service to be ready?

Not by default; it only orders container startup. To wait for readiness, define a healthcheck on the dependency and use depends_on with condition: service_healthy. Applications should also retry connections.

3. How do services in Compose find each other?

Compose creates a project network and registers each service name in Docker's DNS. A service can reach another by its service name and container port, for example db:5432.

4. What is the difference between docker compose stop, down, and down -v?

stop halts containers but keeps them. down stops and removes containers and the project network. down -v additionally removes named volumes declared in the file, deleting stored data.

5. How do you manage different configurations for dev and prod?

Use compose.override.yaml for development, pass extra files with -f for other environments, and use environment variables, env_file, or profiles to vary behaviour.

6. Is Compose suitable for production?

For single-host deployments and small setups it can work, with restart policies and healthchecks. For multi-host scaling, rolling updates, and self-healing across nodes, an orchestrator such as Kubernetes is the usual choice.

Summary

  • Compose describes services, networks, and volumes in one YAML file and runs them with docker compose.
  • The version: key is not needed in Compose v2; the default file is compose.yaml.
  • Use healthchecks with depends_on conditions for real readiness ordering.
  • Keep secrets out of version control and be careful with down -v.

Continue Learning

Previous: Docker Networking | Next: Dockerize Spring Boot App

Was this guide helpful?

CodeWithVenu is free to read, with no paywall. If this guide saved you time or helped you prepare, a small voluntary contribution helps me write more practical tutorials, architecture guides, and interview-prep content.