YAML Anchors and Aliases — Reduce Repeat in Your Configs
If you have ever maintained a Docker Compose file with three services that share the same environment variables, or a Kubernetes manifest where every container has identical resource limits, you have felt the pain of YAML repetition.
YAML has no native "variable" system. No const, no let, no import. Just raw data.
But it does have anchors and aliases — two features that, once understood, dramatically reduce duplication in configuration files.
Anchors (&name) mark a node for reuse. Aliases (*name) reference that node elsewhere. Merge keys (<<:) combine multiple anchors into one structure.
This guide covers exactly how they work, where they shine, and the pitfalls that still trip up experienced developers.
Anchors and Aliases: The Basics
Defining an Anchor
An anchor marks a YAML node so it can be referenced later:
defaults: &defaults
image: nginx:latest
ports:
- "80:80"
The &defaults syntax attaches the name defaults to that mapping node.
Using an Alias
Reference the anchored node with *name:
web:
<<: *defaults
hostname: web-server
The <<: merge key tells YAML to merge the anchor's key-value pairs into the current mapping.
Result
web:
image: nginx:latest
ports:
- "80:80"
hostname: web-server
Without <<:, a bare alias replaces the entire node rather than merging keys. Use <<: *name for merging; use *name for full replacement.
Real-World Docker Compose Example
This is where anchors save the most lines.
version: "3.8"
x-shared: &shared
restart: unless-stopped
networks:
- app-network
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
x-environment: &env
NODE_ENV: production
LOG_LEVEL: info
services:
api:
<<: *shared
image: api:latest
environment:
<<: *env
API_PORT: 3000
worker:
<<: *shared
image: worker:latest
environment:
<<: *env
QUEUE_NAME: tasks
cron:
<<: *shared
image: cron:latest
environment:
<<: *env
SCHEDULE: "*/5 * * * *"
Without anchors, this file would be nearly 60 lines of repetition. With anchors, it is half that size.
The x-shared prefix is a YAML convention — parsers ignore unrecognized top-level keys, so x- namespaces serve as "variable definition" blocks.
Anchors in Kubernetes Manifests
Kubernetes manifests are deeply repetitive. Every container needs resource limits. Every Deployment has metadata. Anchors keep them manageable.
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 3
template:
spec:
containers:
- name: api
image: api:latest
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
- name: sidecar
image: sidecar:latest
resources: # same block repeated
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
With anchors:
x-resources: &resources
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
spec:
containers:
- name: api
image: api:latest
resources: *resources
- name: sidecar
image: sidecar:latest
resources: *resources
Every container inherits the same resource block. When the values change, update them in one place.
Advanced Patterns: Overriding Anchor Values
Anchors support partial overrides through merge keys.
x-base: &base
image: nginx:latest
ports:
- "80:80"
environment:
- ENV=production
web:
<<: *base
ports:
- "80:80"
- "443:443"
environment: # overrides the entire environment block
- ENV=production
- SSL_ENABLED=true
Important: Overriding a mapping key (environment in the example) replaces the entire mapping from the anchor. There is no deep merge — only top-level key replacement.
This limitation surprises many developers. If the anchor defines five environment variables and the override defines two, only those two exist in the final output. The anchor's other three are lost.
Anchors with Lists
Anchors also work on list nodes:
x-ports: &default-ports
- "80:80"
- "443:443"
services:
web:
image: nginx
ports: *default-ports
admin:
image: admin-ui
ports: *default-ports
To extend a list anchor, use merge keys with lists (supported by most parsers):
web:
image: nginx
ports:
- "8080:80" # additional port
- *default-ports # ❌ does not work like this
Lists are not merged by <<:. You must explicitly flatten them, which most YAML libraries do not support natively. This is a genuine limitation — list composition with anchors is awkward in standard YAML.
Nested Anchors
Anchors can reference other anchors:
x-logging: &logging
logging:
driver: json-file
x-monitoring: &monitoring
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
x-full: &full
<<: [*logging, *monitoring]
services:
app:
<<: *full
image: app:latest
The <<: merge key accepts a list of anchors. The resulting mapping contains all keys from both referenced anchors.
Common Pitfalls
Pitfall 1: Anchor Scope
Anchors are scoped to a single YAML file. They do not cross document boundaries (--- separators create new documents where anchors defined earlier are invisible).
# File starts here
x-default: &default
key: value
---
# New document — &default is NOT available here
data:
<<: *default # Error: unknown anchor
If you need shared anchors across files, use file inclusion mechanisms (Docker Compose extends, Kubernetes Kustomize, or a preprocessing step).
Pitfall 2: No Deep Merge
As mentioned earlier, <<: does a shallow merge. Nested mappings under a key are replaced, not merged.
x-base: &base
spec:
containers:
- name: app
web:
<<: *base
spec:
replicas: 3
# spec is overwritten — containers is lost
The entire spec key is replaced. The containers definition from the anchor disappears.
Pitfall 3: Merge Order Matters
When merging multiple anchors, later keys override earlier ones:
x-base: &base
image: nginx
port: 80
x-override: &override
port: 443
web:
<<: [*base, *override]
hostname: web
Result: port: 443 (override wins), image: nginx, hostname: web.
Anchors in CI/CD Pipelines
GitHub Actions workflows benefit from anchors for reusable step configurations:
x-cache: &cache
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
x-notify: ¬ify
uses: slack-action/notify@v2
with:
status: ${{ job.status }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- *cache
- run: npm test
- *notify
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- *cache
- run: npm run build
- *notify
Each job reuses the same cache and notification steps without duplication.
Anchors in Ansible
Ansible playbooks use anchors for reusable task blocks:
x-task-defaults: &task-defaults
become: yes
become_user: deploy
environment:
RAILS_ENV: production
- name: Deploy application
<<: *task-defaults
command: cap production deploy
- name: Restart services
<<: *task-defaults
service:
name: puma
state: restarted
When Not to Use Anchors
Anchors are powerful but not always the right choice:
1. Simple one-off overrides. If you only repeat a value twice, the indirection of anchors adds more syntax than it saves.
2. Cross-file reuse. Anchors cannot span files. Use Kustomize, Helm templates, or a proper templating engine instead.
3. Dynamic values. Anchors are static. If values change at runtime or across environments, use environment variables or template substitution.
4. Complex nesting. If your anchor structure requires three levels of <<: with list merging, the resulting configuration is harder to debug than the duplication it replaced.
Testing Anchored YAML
Anchors and aliases resolve at parse time. The best way to verify your configuration is to parse it and inspect the output:
import yaml
with open("docker-compose.yml") as f:
config = yaml.safe_load(f)
print(yaml.dump(config, default_flow_style=False))
This resolves all anchors and shows the final structure. If something looks wrong, you will see it immediately.
If you are troubleshooting an anchored YAML file that produces unexpected results, paste it into a YAML formatter that resolves anchors — the expanded output makes misconfigured overrides obvious.
For more on YAML syntax quirks that affect anchored structures, see Why Your YAML Is Invalid and How to Fix YAML Indentation Errors.
FAQ
What is a YAML anchor?
A YAML anchor is a named reference to a YAML node, defined with &name syntax. Once defined, the anchored node can be referenced elsewhere in the same file using an alias (*name) or merged into another mapping using the merge key (<<: *name). Anchors reduce duplication by allowing you to define a block once and reuse it in multiple places. Common use cases include shared environment variables in Docker Compose, identical resource limits across Kubernetes containers, and reusable CI/CD job steps.
What is the difference between *name and <<: *name in YAML?
*name is a simple alias that inserts the exact anchored node in place — useful for replacing an entire value with the anchored content. <<: *name is a merge key that spreads the anchor's key-value pairs into the current mapping, allowing overrides. Use *name when you are replacing a scalar or list, like resources: *default-resources. Use <<: *name when you want to inherit configuration keys while potentially overriding some of them, like a Docker Compose service that inherits shared restart policy but adds its own ports.
Can YAML anchors be used across multiple files?
No. Anchors and aliases are strictly scoped to a single YAML file. A document separator (---) also resets the anchor namespace — any anchor defined before --- is invisible in subsequent documents. For cross-file reuse, you need platform-specific mechanisms like Docker Compose extends, Kubernetes Kustomize overlays, Helm templates, or a preprocessing step that concatenates files before parsing.
Do all YAML parsers support anchors and aliases?
Most production-grade YAML parsers support anchors and aliases, but support varies. PyYAML and ruamel.yaml support them fully. Go-based parsers (used by Kubernetes and Docker) also support them. However, some online YAML validators and formatters do not resolve anchors — they may display the raw &name and *name syntax without expanding the references. Always test anchor-heavy YAML with the parser your target platform actually uses, not a generic validator.
Can I override specific keys from a YAML anchor?
Yes, but only at the top level of the merged mapping. When you merge an anchor with <<: *name and then define additional keys, overlapping keys from your local mapping override the anchor's values. However, this is a shallow merge — if a key contains a nested mapping, overriding it replaces the entire mapping, not individual nested keys. There is no deep merge in standard YAML. If you need selective nested overrides, combine multiple fine-grained anchors instead of one large monolithic anchor.
Final Thoughts
YAML anchors and aliases are one of the few features that make large configuration files genuinely maintainable.
Without them, Docker Compose files with multiple services become exercises in copy-paste maintenance. Kubernetes manifests balloon to unmanageable sizes. CI/CD workflows repeat the same setup steps across every job.
The key is knowing when anchors help and when they add complexity. Use them for truly repeated blocks — resource limits, environment variables, logging configs, cache steps. Avoid them for one-off values that happen to look similar.
And always verify the resolved output before deploying. A YAML formatter that expands anchors is the fastest way to catch unexpected merge behavior before it reaches production.