YAML anchors

A YAML anchor is a label — &name — that marks a value so you can reuse it later in the same document with an alias, *name. The parser resolves aliases while reading, so your program receives plain data with the value repeated. Anchors are YAML's own answer to copy-paste.

defaults: &defaults # anchor the mapping
  image: node:22
  retries: 2
  timeout: 10m

unit:
  <<: *defaults # merge every key from defaults
  script: bun test

e2e:
  <<: *defaults
  timeout: 30m # local key wins over the merged one
  script: bunx playwright test

notify_on: &channels ["#ci", "#releases"]
deploy_notify: *channels # alias: the same list again

After parsing, e2e is { image: node:22, retries: 2, timeout: 30m, script: … } and deploy_notify is the same two-item list.

Three pieces

Syntax Name What it does
&name anchor Labels the value that follows it
*name alias Stands for the anchored value, in full
<<: *name merge key Copies the keys of an anchored mapping into the current mapping

An alias reuses a value as is. You can't change one key through a plain alias. The merge key is what lets you take a mapping and override part of it.

How merges resolve

  • Local keys always win. A key written in the mapping beats the same key from <<, whether it comes before or after the merge line.
  • Several merges: earlier wins. In <<: [*base, *gpu], a key present in both comes from base.
  • Merges are shallow. If defaults has env: { A: 1 } and you set env: { B: 2 } locally, the result is { B: 2 }, not both. Nested maps are replaced, not combined.
  • Lists can't be merged. << only works with mappings. There is no YAML syntax to append to an anchored list.

Where anchors work, and where they don't

Where Anchors and aliases <<: merge key Reuse across files
Docker Compose yes yes include, extends
GitLab CI yes yes extends, !reference (not anchors)
GitHub Actions yes (since 2025) no reusable workflows, composite actions
Kubernetes manifests yes yes Helm, Kustomize
Helm values.yaml yes yes values files, _helpers.tpl

Docker Compose pairs anchors with top-level x- keys, which Compose ignores, so you have somewhere to put a shared block that isn't a service:

x-logging: &logging
  driver: json-file
  options: { max-size: 10m }

services:
  api: { image: api:latest, logging: *logging }
  worker: { image: worker:latest, logging: *logging }

Gotchas

  • One document only. An anchor doesn't reach past a --- separator, or into another file.
  • Define before use. An alias must come after its anchor in the file.
  • YAML 1.2 dropped the merge key. Most parsers (js-yaml, PyYAML, Go's yaml.v3, SnakeYAML) still accept it, but a strict 1.2 parser may read << as an ordinary key.
  • Converting to JSON expands everything. JSON has no references, so aliases become copies and the anchors are gone for good.
  • Alias bombs. Nested aliases can expand exponentially (the YAML version of the "billion laughs" attack). Parsers that read untrusted YAML cap alias expansion; check that yours does.

Seeing what an anchor actually expands to

When a merged CI job does something you didn't expect, the fastest check is to look at the expanded data. Comma's free YAML to JSON converter resolves every anchor, alias and merge key so you can see exactly what the tool receives. YAML diff compares two files semantically, so refactoring repeated blocks into anchors shows no changes if you got it right. Both run in your browser, and either result can be published as a link teammates comment on.

Try it

Comma is free: unlimited reports, unlimited commenters, unlimited revision history.

Open the YAML to JSON converter →

Related