YAML Anchors & Aliases: DRY Configuration Guide
Eliminate duplication in your YAML files with anchors, aliases, and merge keys. A practical guide with real-world examples.
Introduction
One of YAML's most powerful and least understood features is its anchor and alias system. Anchors let you define a value or mapping once, and aliases let you reference that definition anywhere else in the same document. This is YAML's answer to the DRY (Don't Repeat Yourself) principle, and it can dramatically reduce duplication in large configuration files.
If you have ever maintained a Kubernetes Helm chart with dozens of repeated labels, a Docker Compose file with shared environment variables, or a CI/CD pipeline with identical job configurations, anchors and aliases can simplify your life. This guide covers everything from basic syntax to advanced merge patterns, complete with practical examples you can adapt to your own projects.
Basic Syntax: Anchors and Aliases
An anchor is created with the & character followed by a name. An alias references an anchor with the * character followed by the same name. When the YAML parser encounters an alias, it substitutes the value of the referenced anchor.
Simple Scalar Anchors
# Define an anchor for a scalar value
database_host: &db_host "db.production.example.com"
# Reference it with an alias
primary_db:
host: *db_host
port: 5432
replica_db:
host: *db_host
port: 5433
# After parsing, both primary_db.host and replica_db.host
# will be "db.production.example.com"In this example, the anchor &db_host is defined on the first line and stores the string "db.production.example.com". Every occurrence of *db_host is replaced with that value during parsing. If you need to change the hostname, you only update it in one place.
Sequence (List) Anchors
# Anchor an entire list
common_labels: &labels
- app: myservice
- team: platform
- env: production
deployment:
metadata:
labels: *labels
service:
metadata:
labels: *labelsMapping (Object) Anchors
# Anchor an entire mapping
defaults: &defaults
timeout: 30
retries: 3
log_level: info
service_a:
name: auth-service
<<: *defaults
service_b:
name: payment-service
<<: *defaultsThis is where things get particularly useful. The << syntax is the merge key. It tells the parser to merge all key-value pairs from the referenced anchor into the current mapping. After parsing, service_a effectively becomes:
service_a:
name: auth-service
timeout: 30
retries: 3
log_level: infoMerge Keys in Depth
The merge key (<<) is technically not part of the core YAML specification -- it is defined in the YAML type repository as a language-independent type. However, it is supported by virtually every major YAML parser and is widely used in practice.
Overriding Merged Values
When you merge a mapping with <<, you can override individual keys by simply redefining them. Keys defined directly in the mapping take precedence over merged values.
defaults: &defaults
timeout: 30
retries: 3
log_level: info
# Override log_level for debugging
debug_service:
<<: *defaults
log_level: debug # overrides "info" from defaults
name: debug-runner
# Result after parsing:
# debug_service:
# timeout: 30
# retries: 3
# log_level: debug
# name: debug-runnerMerging Multiple Anchors
You can merge multiple anchors into a single mapping by passing a list to the merge key. Later entries in the list have lower priority -- the first matching key wins.
base: &base
timeout: 30
retries: 3
logging: &logging
log_level: info
log_format: json
monitoring: &monitoring
metrics_enabled: true
health_check_path: /healthz
service:
<<: [*base, *logging, *monitoring]
name: my-service
# Result:
# service:
# timeout: 30
# retries: 3
# log_level: info
# log_format: json
# metrics_enabled: true
# health_check_path: /healthz
# name: my-serviceReal-World Patterns
Database Configuration
A common pattern in multi-environment configurations is to define a base database configuration and then override specific values for each environment.
# Base database settings
db_defaults: &db_defaults
driver: postgres
pool_size: 10
ssl: true
statement_timeout: 5000
idle_timeout: 300
environments:
development:
database:
<<: *db_defaults
host: localhost
port: 5432
name: myapp_dev
ssl: false # No SSL in dev
pool_size: 5 # Smaller pool in dev
staging:
database:
<<: *db_defaults
host: staging-db.internal
port: 5432
name: myapp_staging
production:
database:
<<: *db_defaults
host: prod-db.internal
port: 5432
name: myapp_prod
pool_size: 50 # Larger pool in prodCI/CD Pipeline Templates
CI/CD configurations often have multiple jobs that share the same base configuration. GitLab CI natively supports YAML anchors, making this a first-class pattern.
.job_template: &job_defaults
image: node:20-alpine
before_script:
- npm ci --cache .npm
cache:
key: "${CI_COMMIT_REF_SLUG}"
paths:
- .npm/
- node_modules/
lint:
<<: *job_defaults
stage: test
script:
- npm run lint
unit_test:
<<: *job_defaults
stage: test
script:
- npm run test:unit
artifacts:
reports:
junit: coverage/junit.xml
integration_test:
<<: *job_defaults
stage: test
script:
- npm run test:integration
services:
- postgres:15Docker Compose Shared Configuration
x-common-env: &common_env
NODE_ENV: production
LOG_LEVEL: info
REDIS_URL: redis://cache:6379
x-healthcheck: &healthcheck
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
services:
api:
image: myapp/api:latest
environment:
<<: *common_env
SERVICE_NAME: api
PORT: "3000"
healthcheck:
<<: *healthcheck
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
worker:
image: myapp/worker:latest
environment:
<<: *common_env
SERVICE_NAME: worker
CONCURRENCY: "4"
healthcheck:
<<: *healthcheck
test: ["CMD", "curl", "-f", "http://localhost:3001/health"]Common Pitfalls and Gotchas
Anchors Are Document-Scoped
Anchors only exist within a single YAML document. If your file uses the --- document separator, anchors defined in one document cannot be referenced in another. This catches many people by surprise when working with multi-document YAML files (which are common in Kubernetes manifests).
# Document 1
---
defaults: &defaults
timeout: 30
service_a:
<<: *defaults # Works fine
# Document 2
---
service_b:
<<: *defaults # ERROR! *defaults is not defined in this documentAliases Create References, Not Copies
In most YAML parsers, aliases create references to the same underlying object, not deep copies. This means mutating a value through one alias can affect all other references to the same anchor. In practice, this rarely causes issues because YAML is typically parsed into an immutable data structure, but it is worth understanding if you are working with a parser that exposes mutable references.
Merge Key Limitations
The merge key (<<) only works for mappings, not for sequences (lists). You cannot use it to merge or concatenate arrays. If you need to combine lists, you must do so at the application level after parsing.
# This does NOT work as expected
base_tags: &base_tags
- app: myservice
- team: platform
extended_tags:
<<: *base_tags # ERROR: merge key is for mappings only
- env: productionCircular References
YAML allows you to create circular references with anchors and aliases, which most parsers will either reject with an error or handle by creating a reference loop. Always be careful not to create accidental cycles.
Not Supported in JSON or TOML
Anchors and aliases are unique to YAML. When you convert a YAML file with anchors to JSON or TOML, the aliases are resolved and the values are duplicated in the output. This means the DRY benefit exists only in the YAML source; the converted output will contain redundant data. For a comparison of these formats, see our YAML vs JSON vs TOML guide.
Best Practices
- Name anchors descriptively: Use meaningful names like
&db_defaultsinstead of&a1. Future maintainers will thank you. - Group anchor definitions at the top: Place all your anchor definitions at the top of the file, clearly separated from the main configuration. This makes it obvious what can be reused.
- Document overrides: When you merge and override a value, add a comment explaining why the override exists.
- Prefer shallow merges: Deeply nested anchor/merge patterns become hard to reason about. If your merge chain is more than two levels deep, consider restructuring.
- Test with our validator: Paste your YAML into the YAML Validator to verify that anchors resolve correctly and the output matches your expectations.
Anchors in Popular Tools
Different tools have varying levels of anchor support. GitLab CI fully supports YAML anchors and even recommends them in their documentation. GitHub Actions, on the other hand, does not support anchors because the workflow parser strips them. Kubernetes fully supports YAML anchors in manifest files. Docker Compose supports them and also provides its own extension fields (x- prefix) mechanism that works alongside anchors.
Before relying on anchors in your configuration, verify that your target tool's YAML parser supports them. For GitHub Actions-specific YAML patterns, see our GitHub Actions YAML Guide.
Further Reading
- YAML 1.2 — Anchors and Aliases
The YAML specification section defining anchor and alias syntax.
- yaml.org
Official YAML project site with links to specifications and resources.
- Kubernetes YAML tips
Kubernetes documentation on working with YAML manifests and object specs.