The basic shape
An anchor declares the value; an alias copies it.
defaults: &defaults
retries: 3
timeout: 30
log_level: info
dev:
<<: *defaults
log_level: debug
prod:
<<: *defaults
retries: 5 After parsing, this resolves to:
defaults:
retries: 3
timeout: 30
log_level: info
dev:
retries: 3
timeout: 30
log_level: debug
prod:
retries: 5
timeout: 30
log_level: info
The &defaults anchor names that map; the
*defaults alias inserts a reference to it; the
<<: merge key tells the parser "merge the
anchored map's keys into the parent." Local keys (like
log_level: debug) override anchored ones.
Anchors work on any value
Anchored list
standard_ports: &ports
- 80
- 443
http_app:
ports: *ports
api_app:
ports: *ports Anchored scalar
app_name: &name billing-service
service_id: *name
display_name: *name
Both service_id and display_name become
the string "billing-service". Useful when the same
value appears in many places and you want one place to change it.
Multi-merge: combining several anchored maps
The merge key accepts a list of aliases. Earlier items take precedence on conflicts:
postgres: &postgres
driver: postgres
port: 5432
logging: &logging
log_level: info
log_destination: stdout
prod_db:
<<: [*postgres, *logging]
host: db.prod.example
log_level: warn
After resolution, prod_db has every key from both
postgres and logging, plus its own
keys, with log_level: warn winning over
log_level: info.
Gotchas
Merge key isn't standard YAML 1.2
The merge key (<<:) was a YAML 1.1 feature kept
by convention. The 1.2 spec doesn't formalize it; almost every
popular parser supports it anyway, but a strict 1.2-only parser
may not. Test in your toolchain.
Anchors don't span files
Each YAML document is a closed scope. You can't define an anchor
in defaults.yaml and use the alias in
prod.yaml. For cross-file reuse, you need a
templating layer or a tool that merges files (Helm,
Kustomize, etc.) before the YAML parser sees them.
Override only works one level deep
The merge key replaces top-level keys, not nested ones. If you want to override a single field inside a nested map, you have to either re-specify the whole map or restructure to put the overridable field at the top level.
Aliases resolve to copies
After parsing, two uses of the same alias produce independent copies of the data. Mutating one in your application code doesn't affect the other. Don't expect JavaScript-style reference semantics.
Forward references aren't allowed
The anchor must appear before any alias that references it. YAML is a streaming format and parsers resolve aliases as they go.
Anchors and merge keys in Kubernetes, Helm, and Ansible
A common question is whether kubectl apply, Helm, and
Ansible actually support YAML anchors, aliases, and the merge key.
They do — each runs a parser that resolves them before the
data reaches the cluster or playbook engine:
| Tool | Parser | Anchors / aliases / <<: |
|---|---|---|
| kubectl | sigs.k8s.io/yaml → go-yaml | Supported |
| Helm | go-yaml | Supported (single file only) |
| Ansible | PyYAML | Supported |
Because resolution happens client-side, the Kubernetes API server only ever stores the fully-expanded document — the anchors are gone by the time the object is created. The catch is the same one as everywhere else: anchors can't span files, so a Helm chart can't anchor in one template and alias in another. Pin a recent go-yaml release; some old versions mishandled merge-key precedence.
When to use anchors vs a templating engine
| Need | Tool |
|---|---|
| Reuse within one file | Anchors + merge keys |
| Reuse across files | Helm / Kustomize / Jinja / etc. |
| Per-environment values | Templating engine |
| Computed values from other inputs | Templating engine |
| Schema validation | JSON Schema (validates either) |
Reference
FAQ
Are anchors and aliases the same as references in JavaScript?
No — they're a serialization trick, not runtime references. After parsing, you have a copy of the data, not a shared object. Mutating the resolved value doesn't affect the anchor's other uses (because there's no anchor anymore by the time your code sees it).
Does the merge key <<: work in every YAML parser?
It's not part of the YAML 1.2 core spec — it's a 1.1 feature that was kept by convention. Most popular parsers (PyYAML, snakeyaml, js-yaml, the npm 'yaml' package, ruby/yaml) support it. Niche parsers may not. Check your parser's docs if portability matters.
Can I anchor a string and reuse it?
Yes. Anchors work on any value — scalars, lists, maps. `name: &n my-app` lets you use `*n` later for the same string. In practice, anchors on scalars are rare; the win is reusing whole map blocks via merge keys.
What's a 'billion laughs' attack?
An aliased anchor that exponentially expands when resolved. `a: &a [1, 1, 1, 1, 1, 1, 1, 1, 1]; b: &b [*a, *a, *a, *a, *a, *a, *a, *a, *a]; c: &c [*b, *b, *b, *b, *b, *b, *b, *b, *b]; ...` — each level multiplies the size 9× and parsers without alias-count limits run out of memory. Most modern parsers cap alias expansion (the 'yaml' npm package limits to 100 by default).
Should I use anchors instead of a templating engine?
For repetition inside a single file, yes — anchors are simpler than introducing Jinja or Handlebars. For repetition across files, no — anchors don't span files. For dynamic values that change per environment, no — anchors are static. Use the right tool.
Do Kubernetes (kubectl), Helm, and Ansible support anchors and the merge key?
Yes — kubectl, Helm, and Ansible all parse anchors (&), aliases (*), and the merge key (<<:), because they each use a parser that supports them: kubectl uses the Go sigs.k8s.io/yaml + go-yaml stack, Helm uses go-yaml directly, and Ansible uses PyYAML. The expansion happens client-side before the data is sent to the API server or applied, so the resolved document is what gets stored — kubectl never sees the anchors. The one caveat: Helm resolves anchors only within a single template file (anchors can't span files or chart values), and very old go-yaml versions had merge-key edge cases, so pin a recent release.