✦ For everyone, free.

Practical knowledge for real and everyday life

Home

17.3.1 Compose Readability

A focused guide to Compose Readability, connecting core concepts with practical Docker and container operations.

Compose readability concerns the specific formatting and structural techniques that keep a Compose file easy to read and maintain as it grows, YAML anchors for avoiding repetition, the include directive for splitting an overly large file, and consistent ordering and commenting conventions, distinct from the higher-level organizational practices of how files relate to each other across environments.

Using YAML anchors to avoid repeated configuration

When several services share a meaningful, identical block of configuration, a YAML anchor and alias avoid repeating that block verbatim for every service, keeping the shared configuration defined once and referenced everywhere it applies:

x-common-logging: &common-logging
  logging:
    driver: json-file
    options:
      max-size: "10m"
      max-file: "3"

services:
  api:
    <<: *common-logging
    image: my-api
  worker:
    <<: *common-logging
    image: my-worker

This keeps the shared logging configuration defined in exactly one place; updating it for both services requires changing only the anchor definition, rather than finding and updating every individual service's own, separately repeated copy.

Extension fields for organizing shared configuration

Compose's x- prefixed extension fields, as shown above, are explicitly reserved for exactly this purpose, defining reusable configuration fragments that are not themselves service definitions but exist purely to be referenced by anchors elsewhere in the file:

x-healthcheck-defaults: &healthcheck-defaults
  interval: 15s
  timeout: 5s
  retries: 3

services:
  api:
    healthcheck:
      <<: *healthcheck-defaults
      test: ["CMD", "curl", "-f", "http://localhost:3000/healthz"]

Grouping these extension field definitions together, typically near the top of the file, makes them easy to locate and review as a distinct, reusable layer of configuration, separate from the individual service definitions that consume them.

Splitting a large file with include

For projects with a Compose file that has grown unwieldy, the include directive allows splitting the definition across multiple files while still treating them as one logical stack, which is a structurally cleaner approach than either accepting an enormous single file or maintaining several separately invoked, disconnected Compose files:

include:
  - path: ./services/api/docker-compose.yml
  - path: ./services/worker/docker-compose.yml

This is particularly useful for a monorepo with several genuinely separate services, each maintaining its own, more focused Compose file, while a top-level file ties them together into one combined, manageable stack definition.

Consistent key ordering within service definitions

Adopting a consistent order for keys within each service definition, image or build first, then ports, then environment, then volumes, then depends_on, and so on, makes every service definition predictable to scan, since a reader knows roughly where to look for any given kind of configuration regardless of which specific service they are reading:

services:
  api:
    image: my-api:1.4.2
    ports:
      - "3000:3000"
    environment:
      - LOG_LEVEL=info
    volumes:
      - app-data:/app/data
    depends_on:
      db:
        condition: service_healthy

This consistency costs nothing functionally but meaningfully improves the experience of reading and reviewing a file with many service definitions, especially for anyone less familiar with the specific project who is encountering the file for the first time.

Using comments to explain non-obvious decisions

A comment explaining why a specific, non-obvious configuration choice was made, a particular resource limit, a deliberately omitted health condition, an unusual network configuration, preserves the reasoning behind it for anyone reading the file later, including the original author after enough time has passed to have forgotten the specific context:

services:
  worker:
    # Intentionally no health check: this is a queue consumer with no
    # HTTP endpoint, and container exit is the correct failure signal here.
    image: my-worker

A comment like this prevents a future reader from mistakenly assuming a missing health check is an oversight needing correction, when it actually reflects a deliberate, considered decision specific to this service's nature.

Avoiding excessive nesting and nested nesting

Compose's structure naturally involves some nesting, but excessively deep or unnecessarily complex nested structures, particularly within deploy or healthcheck blocks, are worth flattening or simplifying where the schema allows, since deep nesting makes a file harder to scan visually and easier to misindent accidentally:

deploy:
  resources:
    limits:
      memory: 512M
      cpus: "1"

This level of nesting is dictated by the schema itself and is unavoidable, but avoiding additional, unnecessary nesting beyond what the schema actually requires, through clear, minimal structure rather than elaborate, deeply layered organization, keeps the file as readable as the underlying format allows.

Alphabetizing or logically grouping services

For a file with many services, either alphabetizing them or grouping them logically, by architectural layer or by team ownership, for instance, makes locating a specific service faster than scanning through an arbitrary, undocumented ordering:

services:
  api:        # presentation layer
  worker:     # background processing
  db:         # data layer
  cache:      # data layer

Choosing either alphabetical or logical grouping and applying it consistently, rather than an unplanned, historical ordering reflecting whatever order services happened to be added in originally, makes the file's organization itself convey useful information to a reader.

Common mistakes

  • Repeating the same block of configuration across many service definitions rather than using a YAML anchor to define it once and reference it everywhere needed.
  • Allowing a single Compose file to grow unwieldy without considering the include directive to split it into more focused, manageable pieces.
  • Using an inconsistent key ordering across different service definitions within the same file, making each one unpredictable to scan.
  • Omitting comments explaining genuinely non-obvious configuration decisions, leaving a future reader uncertain whether something unusual is deliberate or an oversight.
  • Leaving services ordered arbitrarily, by historical addition order, rather than alphabetically or by a deliberate, logical grouping.

Compose readability is improved through specific, low-cost techniques, YAML anchors for shared configuration, the include directive for splitting large files, consistent key ordering, explanatory comments for non-obvious decisions, and deliberate service ordering, each of which makes a growing Compose file meaningfully easier to navigate and maintain without requiring any change to the underlying functionality it describes.

Content in this section