Skip to main content

Last updated: April 28, 2026

YAML Anchors, Aliases & Merge Keys

Reuse data across a YAML file with anchors (&), aliases (*), and merge keys (<<:). Real Docker Compose and GitLab CI examples, plus the deep-merge gotchas that surprise teams.

Written by Mohan Raj Kolavi.

Quick answer

Anchor a value with &name, reference it with *name, and merge a referenced mapping into another with <<: *name. The merge is shallow - sibling keys override merged keys, and nested maps replace rather than recurse. Anchors are part of YAML 1.2; merge keys are supported by PyYAML, ruamel.yaml, the JS yaml package, Docker Compose, and GitLab CI.

Basic anchor and alias syntax

An anchor marks a node with a name. An alias references that node later in the same document. The most common pattern is to anchor a mapping of shared defaults and merge it into several configurations:

basic-anchor.yaml
# Define once with &anchor, reuse with *alias
defaults: &defaults
  adapter: postgres
  host: localhost
  port: 5432

development:
  <<: *defaults
  database: app_dev

test:
  <<: *defaults
  database: app_test

After parsing, both development and test contain adapter, host, port, and their own database key. Edit defaults once and every consumer updates.

Merge keys (<<:) explained

The merge key is the special key << followed by an alias. It tells the parser: copy the keys of the aliased mapping into this mapping. Sibling keys defined directly on the current mapping override the merged values:

override.yaml
# Aliased values can be overridden by sibling keys
defaults: &defaults
  timeout: 30
  retries: 3

production:
  <<: *defaults
  timeout: 60

production.timeout is 60, not the 30 from the merged defaults. The local key always wins.

Merging multiple anchors

You can merge several aliases at once by passing a sequence to the merge key. Earlier entries take precedence over later ones:

multiple-anchors.yaml
# Merge multiple anchors in priority order (left = highest)
base: &base
  region: us-east-1
  timeout: 30

retry: &retry
  retries: 5
  backoff: exponential

prod_service:
  <<: [*base, *retry]
  region: us-west-2

Final prod_service: region: us-west-2 (sibling key wins), timeout: 30 (from *base), retries: 5 and backoff: exponential (from *retry).

Anchors on scalars and lists

Anchors are not limited to mappings. Any node can be anchored, and you can substitute it with a plain alias (no merge key needed):

anchor-scalar.yaml
# Anchors work on scalars too
host: &primary "db-primary.internal"
fallback: *primary
anchor-list.yaml
# Anchors on sequences (arrays)
common_ports: &common_ports
  - 80
  - 443

web:
  ports: *common_ports

api:
  ports: *common_ports

The merge key (<<:) only works when the aliased value is a mapping. For lists and scalars, use a plain alias.

Docker Compose example

Compose supports anchors and merge keys directly in docker-compose.yml and compose.yaml. A common pattern is sharing environment variables across services:

docker-compose.yml
# docker-compose.yml - share env across services
x-shared-env: &shared-env
  TZ: UTC
  LOG_LEVEL: info

services:
  web:
    image: app:latest
    environment:
      <<: *shared-env
      ROLE: web
  worker:
    image: app:latest
    environment:
      <<: *shared-env
      ROLE: worker

The x- prefix tells Compose to ignore the key as a service definition - it is a custom extension field that exists only to host the anchor.

GitLab CI example

GitLab pipelines use a leading . on a job name to mark it as a hidden template that is not run on its own. Combined with anchors and merge keys, this becomes a clean job-template pattern:

.gitlab-ci.yml
# .gitlab-ci.yml - DRY job definitions
.deploy_template: &deploy
  stage: deploy
  image: alpine:latest
  script:
    - ./deploy.sh

deploy_staging:
  <<: *deploy
  environment: staging
  only:
    - main

deploy_prod:
  <<: *deploy
  environment: production
  when: manual

Both deploy_staging and deploy_prod inherit the stage, image, and script, then add their environment-specific overrides.

The deep-merge gotcha

The single most common bug with merge keys: nested maps are replaced wholesale, not deep-merged. If the aliased map has a key whose value is itself a map, redefining that key on the current map drops every nested key the alias provided:

deep-merge.yaml
# Merge key is shallow - nested maps replace, not merge
defaults: &defaults
  database:
    host: localhost
    port: 5432

prod:
  <<: *defaults
  database:
    host: prod.db
# prod.database = { host: "prod.db" }
# port: 5432 is LOST because the whole "database" map was replaced.

If you need recursive merging, pre-process the YAML with a templating tool, or flatten the structure so every distinct value lives at its own top-level key.

What JSON loses on conversion

JSON has no native anchor or reference syntax. When you pass YAML with anchors through any YAML to JSON converter the structural sharing is expanded into duplicated objects:

yaml-to-json.yaml
# Anchors collapse during YAML to JSON conversion
defaults: &d
  retries: 3

dev:
  <<: *d
  name: dev

# Becomes JSON:
# { "defaults": { "retries": 3 },
#   "dev": { "retries": 3, "name": "dev" } }
# The shared structure is duplicated. JSON has no $ref by default.

The output is correct - just larger and harder to refactor. This is one of the strongest arguments for staying in YAML for human-edited configs. Compare side-by-side on the YAML vs JSON page.

Anchor errors (forward references, typos, unsupported merge keys) are silent in many editors. Drop your file into the YAML validator to confirm the parser sees what you expect, or use the YAML formatter to inspect the resolved output before deploying. For more advanced reuse patterns and how to share blocks across files, see the YAML syntax reference.

Frequently Asked Questions

What are YAML anchors and aliases?

An anchor (&name) marks a node so it can be reused. An alias (*name) inserts the anchored node by reference. Together they let you define a value once and reference it many times in the same file, eliminating copy-paste duplication.

What is the YAML merge key (<<:)?

The merge key (<<:) merges the keys of an aliased mapping into the current mapping. It is widely supported by PyYAML, ruamel.yaml, the JS yaml package, Docker Compose, and GitLab CI. The merge is shallow - sibling keys at the current level take precedence over the aliased values.

How do I define a YAML anchor?

Place an ampersand and a name immediately after a key, before its value. For example: 'defaults: &defaults' anchors the mapping that follows under the name 'defaults'. The anchor name must be unique within the document.

How do I reference an anchor in YAML?

Use an asterisk and the anchor name: '*defaults'. The parser substitutes the anchored value at that position. With the merge key '<<: *defaults' the parser merges the keys of the aliased mapping into the current mapping instead of replacing it.

Can I merge multiple YAML anchors at once?

Yes. Pass an array to the merge key: '<<: [*base, *retry]'. The leftmost alias has the highest priority - keys defined in earlier aliases shadow keys from later ones, and any sibling key on the merging map wins over all aliases.

Does the YAML merge key do a deep merge?

No. The merge key is shallow. If the aliased map and the current map both define a key whose value is itself a map, the current map's value replaces the aliased map's value entirely - it does not recurse. To deep-merge nested structures you need an out-of-spec extension or a templating layer.

Are YAML anchors part of the YAML 1.2 specification?

Anchors and aliases are part of YAML 1.2. The merge key (<<:) is technically a YAML 1.1 type, but it is supported by every major modern parser including PyYAML, ruamel.yaml, the npm yaml package, libyaml, and snakeyaml. It is the de facto standard for DRY YAML.

Do YAML anchors work in JSON?

No. JSON has no anchor or reference syntax. When a YAML file with anchors is converted to JSON, every alias is expanded into a full copy of the anchored value. The output is valid but loses the structural sharing that the original YAML expressed.

Where are YAML anchors used in real projects?

Docker Compose uses anchors to share environment variables and labels across services. GitLab CI uses anchors with merge keys for job templates. Kubernetes manifests sometimes use them for shared resource limits. Anywhere a config file repeats the same block, anchors are the canonical fix.

Why is my YAML anchor not working?

Three common causes: the alias appears before the anchor in the document (anchors must be defined first), the parser does not support merge keys (older or strict YAML 1.2 parsers), or the alias is mistyped (anchor names are case-sensitive). Run the file through a validator to confirm the parser sees what you expect.

Can I anchor a list or a scalar in YAML?

Yes. Anchors work on any node - scalars, sequences (lists), and mappings (dictionaries). You cannot use the merge key '<<:' with a sequence or scalar; the merge key is only valid when the aliased value is a mapping. For sequences and scalars use a plain alias '*name' to substitute the value in place.

Should I use YAML anchors or a templating tool like Helm?

Use anchors when the duplication lives in a single file and is purely structural - they keep the file self-describing and parseable by any YAML tool. Reach for a templating layer (Helm, Kustomize, Jinja, envsubst) when you need cross-file reuse, conditional logic, or values that come from outside the YAML.

YAML Anchors, Aliases & Merge Keys (<<:) Explained (2026) | Kolavi Studio