Docker Compose Fundamentals

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

Running one container with docker run is easy; running an application made of an API, a database, a cache and a message broker with the right networks, volumes, environment and start order is not. Docker Compose describes that whole application in one compose.yaml file and manages it with one command. This page covers Compose v5.5.x (the docker compose plugin shipped with Docker Desktop 4.91 or later and installable on Docker Engine 29.8.x) and the Compose Specification it implements; the more advanced how-tos are on Compose in Practice.

Compose, the plugin and the Specification

Learning Docker (chapter 8) introduces docker-compose, the Python tool that grew out of Fig, with a docker-compose.yml starting with version: '2'. All of that is historical:

Then Now

Compose v1: a Python program invoked as docker-compose (2014)

Compose v2 (2020) is a Go rewrite invoked as docker compose — a Docker CLI plugin. Compose v5 (2025) is functionally identical to v2 and adds an official Go SDK. v1 is retired.

File formats 1, 2.x and 3.x, selected with a top-level version: key

One rolling Compose Specification. The version: key is obsolete: Compose ignores it (and warns), so omit it.

docker-compose.yml

compose.yaml is the preferred name (compose.yml, docker-compose.yaml and docker-compose.yml still work; if both exist, compose.yaml wins).

Derived from History and development of Docker Compose and How Compose works. File format 1 files (no top-level services: key) do not run at all with Compose v2 or v5.

docker compose version     # Docker Compose version v5.5.1

The application model

A Compose file describes a project made of:

The Compose application model: a project containing services attached to networks and using volumes and configs and secrets
Top-level element Meaning

services

The containers to run: each service is an image (or a build) plus its runtime configuration, scaled to one or more containers.

networks

Networks the services attach to. Compose creates a default network per project, so services reach each other by service name.

volumes

Named volumes, created on first up and kept across down unless you pass -v.

configs

Non-sensitive configuration files mounted into services.

secrets

Sensitive data mounted as files under /run/secrets/ (see Compose in Practice).

include, models, name

Other Compose files to import, AI models for Docker Model Runner, and an explicit project name.

A first compose.yaml: a web application and PostgreSQL

# compose.yaml -- development only: the credentials below are placeholders, not real secrets
name: orders

services:
  api:
    build: .                                   # or: image: ghcr.io/myorg/orders-service:1.4.0
    ports:
      - "127.0.0.1:8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/orders
      SPRING_DATASOURCE_USERNAME: orders
      SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD:-dev-only-password}
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: "1.5"
          memory: 768M

  db:
    image: postgres:18.1
    environment:
      POSTGRES_DB: orders
      POSTGRES_USER: orders
      POSTGRES_PASSWORD: ${DB_PASSWORD:-dev-only-password}
    volumes:
      - db-data:/var/lib/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U orders -d orders"]
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 10s

  adminer:
    image: adminer:5
    ports:
      - "127.0.0.1:8081:8080"
    profiles: ["tools"]                        # only started with --profile tools

volumes:
  db-data:
docker compose up -d --build        # build api, create network and volume, start db, wait until healthy, start api
docker compose ps
docker compose logs -f api
docker compose exec db psql -U orders -d orders
docker compose --profile tools up -d
docker compose down                 # stop and remove containers and the network; keep the volume
docker compose down -v              # ... and remove the named volumes too

Service essentials

Attribute Notes

image / build

Run an existing image, or build one (build: ., or build: {context: ., dockerfile: Dockerfile, target: runtime, args: {…​}}). With both, build produces an image tagged with image.

ports

"HOST:CONTAINER" strings; quote them (YAML reads 22:22 as a base-60 number). Bind to 127.0.0.1 in development.

environment / env_file

Variables for the container. env_file loads a file into the container; .env in the project directory feeds interpolation of the Compose file itself.

volumes

Named volumes (db-data:/path), bind mounts (./src:/app/src), or the long syntax with type:.

networks

Extra or custom networks; aliases per network.

depends_on

Start order. With condition: service_healthy Compose waits for the dependency’s healthcheck; service_completed_successfully waits for a one-shot job; restart: true restarts the dependent service when the dependency is updated.

healthcheck

Same fields as the Dockerfile HEALTHCHECK; overrides the image’s.

restart

no, always, on-failure[:n], unless-stopped.

command / entrypoint

Override the image’s CMD / ENTRYPOINT.

deploy.resources

limits and reservations for CPUs and memory, applied by docker compose up.

profiles

Services started only when a listed profile is active.

stop_grace_period

How long to wait after SIGTERM before SIGKILL (default 10s).

Startup order and readiness

sequenceDiagram participant CLI as docker compose up participant DB as db (postgres) participant API as api participant ADM as adminer (profile tools) CLI->>CLI: create network orders_default and volume orders_db-data CLI->>DB: create and start loop every 5s until healthy (max 10 retries) CLI->>DB: healthcheck pg_isready DB-->>CLI: starting ... healthy end CLI->>API: create and start (depends_on db service_healthy) Note over ADM: not started: profile "tools" inactive CLI-->>CLI: all requested services running

depends_on without a condition only orders container creation: the database process may not be accepting connections yet. Give dependencies a real healthcheck and use condition: service_healthy; keep retry logic in the application as well, because dependencies can also restart later. Derived from Control startup and shutdown order.

Project names

The project name prefixes every resource Compose creates (orders-api-1, orders_default, orders_db-data) and lets the same file run twice on one host. It is taken from, in order: -p/--project-name, the COMPOSE_PROJECT_NAME variable, the top-level name: attribute, and finally the directory name. Derived from Specify a project name.

Environment variables and interpolation

Two different mechanisms are often confused:

  • Interpolation — ${VAR} in compose.yaml is replaced before the file is used, from the shell environment and the .env file next to the Compose file. Default and error forms: ${VAR:-default} (default if unset or empty), ${VAR-default} (if unset), ${VAR:?message} (fail if unset or empty).

  • Container environment — environment: and env_file: set variables inside the container.

When a variable is set in several places, the container sees (highest precedence first): docker compose run -e, then environment/env_file values interpolated from the shell or .env, then a literal environment value, then env_file, then the image’s ENV.

cat > .env <<EOF
DB_PASSWORD=local-placeholder
EOF
docker compose config          # the fully resolved model: interpolated, merged, validated
docker compose config --environment

Derived from Interpolation and Environment variables precedence. docker compose config is the fastest way to find out what Compose will actually do.

The CLI at a glance

docker compose up -d --build --wait   # start in the background; --wait blocks until services are healthy
docker compose ps -a
docker compose logs -f --tail 100 api
docker compose exec api sh            # a shell in a running service container
docker compose run --rm api ./mvnw -q test   # a one-off container for the service
docker compose restart api
docker compose pull                   # fetch newer images for image-based services
docker compose watch                  # sync or rebuild on file changes (see Compose in Practice)
docker compose down -v --remove-orphans

The Learning Docker chapter 8 example, rewritten

The book links a web container to a Redis container with links: in a Compose v1 file run by docker-compose (historical, shown for comparison only):

# 2015: docker-compose.yml for Compose v1 (file format 1, no services key) -- historical
web:
  build: .
  links:
    - redis
  ports:
    - "5000:5000"
redis:
  image: redis

The same application in the Compose Specification needs no links — the project network and its DNS resolve redis by service name — no version: key, and a pinned image:

# compose.yaml, run with: docker compose up -d
services:
  web:
    build: .
    ports:
      - "127.0.0.1:5000:5000"
    depends_on:
      - redis
  redis:
    image: redis:8.2

Derived from Services top-level elements (build, ports, depends_on, image) and History and development of Docker Compose (Compose v1 and the file formats it replaced).