If you’ve ever copy-pasted the same 10 lines of YAML into three different places in a config file, you’ve felt the pain YAML anchors are designed to solve. An anchor lets you define a value once and reference it from anywhere in the same document. Change the value in one place, and every reference updates.
This post walks through how anchors and aliases work, when to use them, and the edge cases that catch people out.
The basic syntax
A YAML anchor is a name you give to a node:
defaults: &defaults
retries: 3
timeout: 30
log_level: info
The &defaults is the anchor. It attaches to the value of defaults, which is a map. You can put anchors on any node — a string, a number, a list, a map, a sequence.
An alias is a reference to an anchor:
production:
<<: *defaults
region: us-east-1
replicas: 5
staging:
<<: *defaults
region: us-west-2
replicas: 2
The <<: *defaults is the “merge key”. It says “take everything from the anchor called defaults and merge it into this map.” The result is the same as if you’d written out the contents of defaults inline:
production:
retries: 3
timeout: 30
log_level: info
region: us-east-1
replicas: 5
But if you change retries in defaults, every place that uses the anchor updates. That’s the whole point.
Anchors on single values
Anchors don’t have to merge into a map. You can anchor a single value and reference it:
api_version: &api "v2.1.0"
services:
auth:
version: *api
billing:
version: *api
reports:
version: *api
Now changing the version of the API is a one-line change. The references all follow.
You can also anchor a list:
allowed_origins: &origins
- https://app.example.com
- https://admin.example.com
- https://staging.example.com
cors:
web: *origins
api: *origins
The list is reused verbatim.
When anchors help
Anchors shine when you have shared defaults across multiple instances. Kubernetes manifests, docker-compose files, and CI matrices are full of this pattern.
A note for 2026: Helm and Kustomize dominate Kubernetes templating now, and they operate outside YAML’s anchor system — they generate YAML, they don’t parse it. If you’re on Helm or Kustomize, you don’t need anchors (you have values.yaml and patch overlays instead). Anchors are still the right call for plain kubectl apply -f workflows, GitHub Actions matrices, docker-compose files, and any hand-authored config that doesn’t go through a template engine.
A Kubernetes example — three services with the same resource limits:
base_resources: &base_resources
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
apiDeployment:
spec:
template:
spec:
containers:
- name: api
image: myapp/api:1.2.3
resources: *base_resources
workerDeployment:
spec:
template:
spec:
containers:
- name: worker
image: myapp/worker:1.2.3
resources: *base_resources
A docker-compose example — same environment for dev and prod:
common_env: &common_env
DATABASE_URL: postgres://db.internal/app
REDIS_URL: redis://cache.internal:6379
LOG_LEVEL: info
services:
api:
environment:
<<: *common_env
ENVIRONMENT: dev
worker:
environment:
<<: *common_env
ENVIRONMENT: dev
In both cases, changing the common env is a one-line edit.
The gotchas
Anchors are powerful but have a few sharp edges.
Anchors are local to a document. A multi-document YAML file (with --- separators) has its own scope for anchors. An anchor defined in document one cannot be referenced from document two. If you’re using tools that produce multi-doc YAML (like kubectl get -o yaml with multiple resources), you can’t use anchors to share values across them.
Anchors and merging interact with overrides in surprising ways. If both the anchor and the referencing map define the same key, the referencing map’s value wins:
defaults: &defaults
retries: 3
timeout: 30
production:
<<: *defaults
retries: 10 # this wins
So production.retries is 10, not 3. That’s the expected behaviour, but it bites people who expect “last one wins” or “first one wins” depending on the order.
Keys in merged maps can’t be removed. If defaults has a legacy_setting: true key and you want to “un-set” it in production, you can’t. Merge only adds keys; it never removes them. Workaround: don’t include the key in the anchor.
Anchors can’t reference themselves. A self-referential anchor creates a cycle. Most parsers detect this and throw an error. If you ever get a “recursive anchor” error, look for an alias pointing back to the anchor that contains it.
Anchors and JSON don’t mix. JSON has no concept of anchors. If you convert YAML to JSON with anchors, the anchors are expanded (the JSON gets larger) or dropped (the JSON is smaller). Either way, the result is bigger than the YAML. If you need a JSON version, you usually need a different config source.
Some linters complain about anchors. yamllint defaults to disabling anchors (anchors: disable) for style reasons. If you’re using anchors, you’ll need to either configure your linter or accept the warnings. Most teams that use anchors consider them worth the lint friction.
Anchors work on the parsed object graph, not the text. If you sort keys alphabetically with a tool like yq and then re-serialise, anchors are preserved by reference, not by the visible key order. The output is still correct, but it can be visually surprising — the anchor shows up in one place, the alias in another, and they refer to the same object.
Verifying anchors in your config
The fastest way to check that your anchors are working is to load the YAML in a parser that resolves them and print the result. The YAML validator on DevSpeedTools shows the resolved object graph — if your anchors are wired up correctly, you’ll see the same values everywhere.
For more involved inspection, the YAML statistics tool tells you the total number of unique vs. duplicated keys, which is a good proxy for “are my anchors actually deduplicating anything?”
For an interactive editor experience, VS Code with the Red Hat YAML extension will resolve anchors on hover and show you where each alias points.
The TL;DR
Anchors are YAML’s version of a variable. They work at parse time, not at serialise time, so the file you write contains the anchor once and the references in many places. The parser expands them to the same value.
Use them for shared defaults. Don’t use them to try to share state across documents. Don’t expect JSON conversion to preserve them. And when in doubt, run the file through a validator to confirm the expansion matches your intent — anchors that silently produce the wrong value are the kind of bug that ships to production and lives there for a year.