Docker Compose YAML Anchors and Aliases for DRY Configuration
AI generated
FROM
RUN
Docker · Docker Compose · YAML · Configuration
YAML Anchors and Aliases
DRY configuration in Docker Compose

Once a compose.yaml contains more than three or four services, logging configuration, environment variables and healthcheck definitions repeat almost identically in every block. YAML anchors, aliases and merge keys let you define these blocks once and reuse them everywhere, without Docker Compose needing its own dedicated feature for it.

16 min read Anchors · Aliases · Merge Keys · Extension Fields Docker Compose 2.x

1. Why repetition in compose.yaml becomes a problem

The more services a Docker Compose file contains, the more certain blocks repeat. Logging drivers, restart policies, healthcheck definitions and entire environment variable lists show up identically or nearly identically in almost every service. If a setting changes, for example the maximum log file size, the same change has to be repeated in five, ten or more places, which invites mistakes and makes reviews harder.

This is exactly where YAML anchors and aliases come in. YAML, the format behind every compose.yaml, has long supported its own referencing syntax that lets a block be defined once and reused at any number of places in the document. Docker Compose itself does not need its own feature for this, because the anchor and alias syntax is already part of the YAML specification and is understood by every conforming YAML parser, including the one Docker Compose uses internally.

The advantage over plain repetition is obvious: changes at a central location automatically apply to every service that references the corresponding anchor. This not only reduces file size, but above all the risk that a service gets forgotten or misconfigured during manual copying. For teams with multiple microservices or a Magento stack with PHP-FPM, Nginx, Node and several databases, this is a direct maintainability win.

2. YAML anchors and aliases: the basics

An anchor is defined with the & character before a key name, for example &common-logging. This anchor marks the following block as reusable. An alias, introduced with the * character, references this block at another point in the document and inserts it exactly as it was defined at the anchor. These two characters, & and *, form the complete vocabulary for simple reuse in YAML.

Important to understand: anchors and aliases are a pure YAML feature, not a Docker Compose specific extension. That means they work regardless of where in the document they sit, as long as the anchor is defined before the alias. Inside a compose.yaml, anchors can be placed at the top level, inside a services block, or in extension fields specifically created for that purpose, which matters for organizing the file.


# compose.yaml — basic anchor and alias usage
services:
  api:
    image: myapp/api:latest
    logging: &default-logging
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  worker:
    image: myapp/worker:latest
    logging: *default-logging   # Reuses the exact same logging block

  scheduler:
    image: myapp/scheduler:latest
    logging: *default-logging   # Same logging config, defined only once

In this example, the logging configuration is defined once at the api service and reused exactly through the default-logging anchor in worker and scheduler. If the max-size value changes, a single change at the anchor location is enough, all three services automatically pick up the new configuration on the next docker compose up.

3. Merge keys: extending an anchor instead of fully replacing it

A plain alias inserts the referenced block unchanged, which is not enough in many cases. Often a service needs most of the settings from a shared block, but with one or two different values. For this case there is the merge key, written as <<: *anchor-name. The merge key inserts all keys of the referenced anchor into the current mapping and at the same time allows individual keys to be overridden right after it.

This combination of merge key and local override is the real added value of YAML anchors in Docker Compose, because real services are rarely one hundred percent identically configured. A typical pattern: a shared base block defines restart, logging and network settings, while each service inherits this base through the merge key and only adds its own image plus individual environment variables.


# compose.yaml — merge key extends a common base
x-common-service: &common-service
  restart: unless-stopped
  networks:
    - backend
  logging:
    driver: json-file
    options:
      max-size: "10m"

services:
  api:
    <<: *common-service
    image: myapp/api:latest
    environment:
      SERVICE_NAME: api

  worker:
    <<: *common-service
    image: myapp/worker:latest
    environment:
      SERVICE_NAME: worker
    restart: on-failure   # Overrides the inherited "unless-stopped"

networks:
  backend:

In the example, the worker service inherits all settings from common-service, but specifically overrides the restart policy to on-failure. This combination of central definition and local override makes merge keys significantly more flexible than plain aliases and is the reason why most production compose.yaml files with many services rely on this pattern.

4. Extension fields: the x- prefix as a clean storage place

Docker Compose validates the root level of a compose.yaml against a fixed schema that only allows certain top level keys such as services, networks, volumes and secrets. A custom key like common-service without a prefix would cause a validation error. For this reason, Docker Compose supports extension fields, top level keys starting with x- that Compose ignores while parsing, but that are fully valid as a storage place for anchors.

The x-name prefix convention is not a Docker Compose invention, but follows the same logic as x- headers in HTTP or x- prefixes in other configuration formats: a reserved namespace for extensions that the actual schema is allowed to ignore. For organizing large compose.yaml files, it is a good idea to bundle all shared anchors under several x- keys at the top of the file, for example x-common-service, x-healthcheck-defaults and x-logging-defaults, instead of scattering them between the services.


# compose.yaml — organizing anchors under x- extension fields
x-healthcheck-defaults: &healthcheck-defaults
  interval: 10s
  timeout: 5s
  retries: 5
  start_period: 30s

x-php-base: &php-base
  build:
    context: .
    dockerfile: docker/php/Dockerfile
  volumes:
    - ./src:/var/www/html
  networks:
    - backend

services:
  php-fpm:
    <<: *php-base
    healthcheck:
      <<: *healthcheck-defaults
      test: ["CMD", "php-fpm-healthcheck"]

  php-worker:
    <<: *php-base
    command: ["php", "bin/console", "worker:run"]
    healthcheck:
      <<: *healthcheck-defaults
      test: ["CMD", "pgrep", "-f", "worker:run"]

networks:
  backend:

5. Practical example: shared logging configuration

Logging is one of the most common examples of useful YAML anchors in practice, because almost every service needs the same logging strategy. Without anchors, the json-file driver with max-size and max-file would have to be manually repeated in every single service. With a central anchor, a single definition is enough, included through a merge key or a direct alias in every service, and a later adjustment to log rotation automatically affects the entire stack.

Especially in production like local development environments where multiple containers write logs in parallel, a central logging configuration also prevents individual services from accidentally running without a size limit and letting the Docker log files on the host grow uncontrolled. This is a detail that is easily overlooked when copied manually, but automatically applies to all services with a central anchor definition.

6. Practical example: shared environment variables and healthchecks

A second common use case is environment variables shared by multiple services, for example database credentials or a shared API endpoint. Instead of listing these variables again in every service, you define an anchor with the shared values and only add each service's individual variables through a second environment section or an env_file reference.

Healthchecks also benefit strongly from anchors, because interval, timeout and retries rarely differ in a stack with several similar services. Only the actual test command is usually service specific. With a merge key, the healthcheck framework can be defined once, while each service only adds its own test command, which ensures consistency of the healthcheck parameters across the entire stack.


# compose.yaml — shared environment variables via anchor
x-db-credentials: &db-credentials
  DB_HOST: mysql
  DB_PORT: "3306"
  DB_NAME: magento
  DB_USER: magento

services:
  php-fpm:
    image: myapp/php:8.4-fpm
    environment:
      <<: *db-credentials
      DB_PASSWORD: ${DB_PASSWORD}
      APP_ENV: development

  cron:
    image: myapp/php:8.4-cli
    command: ["php", "bin/magento", "cron:run"]
    environment:
      <<: *db-credentials
      DB_PASSWORD: ${DB_PASSWORD}
      APP_ENV: cron

7. Limits of anchors compared to include and override files

YAML anchors solve repetition within a single file, but they do not work across multiple files. An anchor defined in compose.yaml cannot be referenced in compose.override.yaml, because YAML parses every file as its own standalone document. For reuse across file boundaries, Docker Compose instead offers the include directive or multiple -f flags on the docker compose command, which operate on a completely different level than YAML anchors.

Another point: anchors cannot be applied conditionally. There is no way to include an anchor only under certain conditions, for example depending on a Compose profile. Anyone who needs conditional configuration still has to work with profiles, separate override files, or environment variables combined with the default value syntax ${VAR:-default}. YAML anchors are a tool against textual repetition, not a tool for conditional logic.

8. Common mistakes when using anchors

The most common mistake is using an alias where a merge key was actually needed. A direct alias like logging: *default-logging replaces the entire value, while a merge key with <<: *anchor inserts the keys into the surrounding mapping and allows local overrides. Anyone who tries to define additional keys in the same mapping after a direct alias gets a YAML parser error, because an alias represents a complete value and does not tolerate further sibling keys in the same block.


# WRONG: alias replaces the whole value, cannot add sibling keys after it
services:
  api:
    logging: *default-logging
    # driver: override   <- would be a duplicate mapping key, invalid

# RIGHT: merge key allows extending and overriding
services:
  api:
    logging:
      <<: *default-logging
      driver: syslog   # Overrides only the driver, keeps other options

A second common mistake concerns ordering: an anchor must be defined before the first alias that references it in the YAML document. If the anchor appears further down in the file than the alias, the parser reports an error, because the reference does not yet exist at the time it is processed. For this reason it makes sense to bundle all shared anchors under extension fields right at the beginning of the compose.yaml, before the services block.

9. Anchors compared to alternatives

Besides YAML anchors, there are other ways to reduce repetition in Docker Compose configurations. Each approach has a different scope, and the choice depends on whether the repetition occurs within a single file or across multiple files.

Approach Scope Conditional logic possible Use case
YAML anchors/aliases Within a single file No Repeated blocks like logging, healthchecks
Merge keys (<<) Within a single file No Base blocks with local overrides
include directive Across multiple files Partially (per file) Entire service definitions from other files
compose.override.yaml Across multiple files Yes, per environment Dev/test/CI specific deviations
${VAR:-default} Individual values Yes, per variable Parameterizing individual configuration values

In practice these approaches do not exclude each other, they complement each other. YAML anchors reduce repetition within a file, while override files and the include directive avoid repetition across multiple environments. A well structured stack typically combines anchors for shared service building blocks with a compose.override.yaml for environment specific adjustments.

Mironsoft

Docker Compose architecture and multi service stacks

A compose.yaml that is not copy pasted for every service?

We restructure existing Docker Compose stacks with anchors, merge keys and extension fields, reduce repetition and make changes in one place instead of ten.

Compose refactoring

Converting existing stacks to anchors and merge keys without changing behavior

Extension fields

Introducing shared base blocks for logging, healthchecks and networks

Magento stacks

Consistently configuring PHP-FPM, cron, worker and Node build services

10. Summary

YAML anchors, aliases and merge keys solve a problem that every growing compose.yaml runs into sooner or later: identical blocks for logging, healthchecks and environment variables that get repeated manually in every service. With the & character, a block is marked as an anchor, with * it is referenced as an alias, and with the merge key << an anchor can be inserted into a mapping while also being locally overridden. Extension fields with the x- prefix provide a clean, Compose ignored storage place for this at the top of the file.

The biggest win lies in maintainability: a change at a central anchor location automatically applies to all referencing services, instead of having to be manually applied in multiple places. For repetition across multiple files, for example between compose.yaml and compose.override.yaml, anchors are not suitable, here the include directive or separate override files take on that role instead.

YAML Anchors and Aliases in Docker Compose — The Essentials at a Glance

Anchor (&)

Marks a block as reusable, directly at the point of definition in the document.

Alias (*)

Inserts the referenced block exactly and unchanged at another point.

Merge key (<<)

Inserts the keys of an anchor into a mapping and allows local overrides.

Extension fields (x-)

Top level keys ignored by Compose, an ideal storage place for shared anchors.

11. FAQ: YAML Anchors and Aliases in Docker Compose

1What are YAML anchors and aliases?
A native YAML feature for defining and reusing blocks once via & and *.
2Alias vs. merge key?
Alias replaces fully, merge key inserts keys and allows local overrides.
3Where do shared anchors belong?
Under x- extension fields at the top of the file, before the services block.
4What are extension fields?
Top level keys with x- prefix, ignored by Compose, ideal anchor storage place.
5Do anchors work across multiple files?
No, only within the same file. For multiple files: include directive or override files.
6Conditional configuration with anchors?
Not possible, use profiles, override files or ${VAR:-default} instead.
7Error with extra keys after an alias?
Alias replaces fully, no sibling keys allowed, a merge key is needed instead.
8Must the anchor come before the alias?
Yes, YAML processes top to bottom, the anchor must be defined first.
9Suitable for shared healthchecks?
Very well, define shared parameters centrally, only add test per service.
10Do many anchors slow down parsing?
Not measurably, YAML parsers are optimized for this.