YAML Anchors and Aliases: Stop Repeating Yourself (DRY Config)
If you maintain Docker Compose files, Kubernetes manifests, or CI/CD pipelines, you have almost certainly copy-pasted the same environment variables, resource limits, or job templates dozens of times. YAML anchors and aliases exist precisely to fix that problem. Here is everything you need to know.
The Problem: Config Duplication Is Everywhere
Consider a typical Docker Compose file for a web application with three services: a web server, a background worker, and a scheduler. All three need the same environment variables - database URL, Redis URL, secret key - and the same logging configuration. Without anchors, you end up writing this:
services:
web:
image: myapp:latest
environment:
DATABASE_URL: postgres://db:5432/myapp
REDIS_URL: redis://redis:6379
SECRET_KEY: changeme
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
worker:
image: myapp:latest
environment:
DATABASE_URL: postgres://db:5432/myapp # duplicated
REDIS_URL: redis://redis:6379 # duplicated
SECRET_KEY: changeme # duplicated
logging:
driver: json-file
options:
max-size: "10m" # duplicated
max-file: "3" # duplicated
scheduler:
image: myapp:latest
environment:
DATABASE_URL: postgres://db:5432/myapp # duplicated again
REDIS_URL: redis://redis:6379
SECRET_KEY: changeme
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
When you need to rotate SECRET_KEY or change the database URL, you must update it in three places. Miss one and you have a subtle bug. YAML anchors eliminate this entirely.
Anchors and Aliases: The Core Syntax
YAML provides two operators for reuse:
&anchor-name- defines an anchor, marking a node for later reference*anchor-name- creates an alias, inserting the anchored value verbatim
The simplest case - reusing a scalar value:
default_image: &image myapp:latest
services:
web:
image: *image # expands to: image: myapp:latest
worker:
image: *image # expands to: image: myapp:latest
Anchors can be placed on any YAML node: scalars, sequences (lists), and mappings (objects). The anchor name can be any alphanumeric string with hyphens or underscores. The & symbol goes immediately before the anchor name with no space.
The Merge Key Operator: <<
Anchors by themselves replace entire nodes. The real power comes from the merge key operator <<, which lets you merge an anchored mapping into another mapping while overriding individual keys:
defaults: &defaults
adapter: postgres
host: localhost
port: 5432
pool: 5
development:
<<: *defaults
database: myapp_dev
test:
<<: *defaults
database: myapp_test
pool: 2 # override the pool size for test
production:
<<: *defaults
host: prod-db.example.com # override host
database: myapp_prod
pool: 20 # override pool
After parsing, development is equivalent to:
development:
adapter: postgres
host: localhost
port: 5432
pool: 5
database: myapp_dev
And production gets adapter: postgres, port: 5432 from the anchor, but its own host, database, and pool values. Keys defined in the local mapping take precedence over keys from the anchor. This is the fundamental rule of the merge key operator.
Real Example: Docker Compose with Anchors
Here is the Docker Compose file from the introduction, rewritten using anchors:
x-common-env: &common-env
DATABASE_URL: postgres://db:5432/myapp
REDIS_URL: redis://redis:6379
SECRET_KEY: changeme
x-logging: &logging
driver: json-file
options:
max-size: "10m"
max-file: "3"
services:
web:
image: myapp:latest
environment:
<<: *common-env
logging:
<<: *logging
worker:
image: myapp:latest
environment:
<<: *common-env
WORKER_CONCURRENCY: "4" # extra key for worker only
logging:
<<: *logging
scheduler:
image: myapp:latest
environment:
<<: *common-env
logging:
<<: *logging
The x- prefix is a Docker Compose convention for extension fields that are not interpreted as service definitions. This is the standard pattern for defining reusable blocks at the top of a Compose file.
The
x-extension field convention is not YAML - it is a Docker Compose feature. In plain YAML for other tools (like Kubernetes or Ansible), you would typically define anchors under a custom top-level key or use a YAML document header block.
Real Example: GitHub Actions CI/CD
YAML anchors are extremely useful in CI/CD pipelines where multiple jobs share common steps:
x-node-setup: &node-setup
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
jobs:
lint:
runs-on: ubuntu-latest
steps:
- *node-setup # note: list item alias, not merge
- run: npm run lint
test:
runs-on: ubuntu-latest
steps:
- *node-setup
- run: npm test
build:
runs-on: ubuntu-latest
steps:
- *node-setup
- run: npm run build
Note the difference here: when the anchor is a sequence (list), you use - *anchor-name to insert it as a list item. This inserts the entire anchored sequence as a single element. If you want to merge sequences, you need to use a different approach or restructure your anchor.
Real Example: Rails Database Configuration
The Rails database.yml file is the canonical example of YAML anchors in the wild:
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: <%= ENV["DB_USERNAME"] %>
password: <%= ENV["DB_PASSWORD"] %>
host: <%= ENV["DB_HOST"] %>
development:
<<: *default
database: myapp_development
test:
<<: *default
database: myapp_test
staging:
<<: *default
database: myapp_staging
pool: 10
production:
<<: *default
database: myapp_production
pool: 25
If you open the config/database.yml in any Rails app generated with PostgreSQL, you will see exactly this pattern. It is the framework's official recommendation.
Step-by-Step: Adding Anchors to an Existing Config
- Identify duplicated blocks. Look for mappings that appear identically (or near-identically) in multiple places. Common candidates: environment variables, resource limits, labels, logging config, timeout settings.
- Extract the common block. Move it to a top-level key (with an
x-prefix in Docker Compose, or any descriptive name in plain YAML) and add&anchor-nameafter the key. - Replace the original occurrences. Use
<<: *anchor-nameto merge the block. Add any per-instance overrides as sibling keys after the merge. - Validate the result. Run the file through a YAML parser or validator to confirm it parses correctly.
python3 -c "import yaml; yaml.safe_load(open('docker-compose.yml'))"is a quick sanity check. - Test the actual tool. Run
docker compose configto see the fully expanded YAML, orkubectl apply --dry-run=clientfor Kubernetes manifests. Confirm the expanded output matches your expectations.
Merging Multiple Anchors
You can merge more than one anchor into a single mapping by passing a list to <<:
x-env: &env
DATABASE_URL: postgres://localhost/mydb
REDIS_URL: redis://localhost:6379
x-limits: &limits
cpus: '0.5'
memory: 512M
services:
web:
image: myapp:latest
environment:
<<: *env
deploy:
resources:
limits:
<<: *limits
memory: 1G # override just memory for web
When merging multiple anchors, use a YAML sequence for the merge key:
combined:
<<:
- *anchor-one
- *anchor-two
local-key: local-value
In this case, anchor-one takes precedence over anchor-two for conflicting keys, and local-key takes precedence over both.
Validate Your YAML Instantly
Paste any YAML with anchors and aliases into our free validator to check syntax, see parse errors, and confirm your anchors expand correctly. 100% client side.
Open YAML ValidatorImportant Limitations to Know
YAML anchors are powerful but have constraints that trip up developers who do not know about them:
- Same document only. Anchors cannot reference nodes in a different YAML file. If you need cross-file reuse, use a tool like Helm (for Kubernetes), Jsonnet, or CUE, which are specifically designed for this purpose.
- Anchors are resolved at parse time. The anchor definition must appear before any alias that references it in the document. A forward reference to an anchor that has not been defined yet is a parse error.
- Merge key is a YAML 1.1 feature. The
<<merge key is specified in YAML 1.1 but was NOT included in the YAML 1.2 specification. Most parsers support it anyway, but strictly compliant YAML 1.2 parsers may reject it. In practice, all major tools (PyYAML, Go's yaml.v3, js-yaml, Ruby's Psych) support it. - Tool support varies. Kubernetes'
kubectldoes not support YAML anchors (it processes YAML files individually through a Go YAML parser that strips anchors). Anchors only work in contexts where a single YAML file is parsed at once by a supporting parser. Docker Compose supports anchors. Ansible supports anchors. GitHub Actions supports anchors in workflow files. - Anchors on sequences use different syntax. Merging lists requires special handling; the
<<merge key only works for mappings (objects), not sequences (arrays).
Frequently Asked Questions
Can I use anchors across multiple YAML files?
No. YAML anchors are scoped to a single document. For multi-file reuse, you need a higher-level templating tool. In Kubernetes, Helm uses Go templates with {{ template "common.labels" . }}. In Terraform, you use modules. In Ansible, you use roles and variable files. Jsonnet and CUE are dedicated configuration languages that solve cross-file reuse natively.
Does Kubernetes support YAML anchors in manifests?
Not directly. The kubectl CLI parses YAML using a strict Go YAML parser that does not preserve anchors after parsing. Each manifest file is processed independently. If you need anchor-like reuse in Kubernetes, use Helm, Kustomize, or a GitOps tool that preprocesses YAML before applying it. Kustomize's commonLabels and patches cover many of the same use cases as anchors.
What is the difference between an alias and a merge?
An alias (*anchor-name) replaces the entire node with the anchored value. If you alias a mapping, you get that exact mapping with no ability to override individual keys. A merge (<<: *anchor-name) inserts the anchored mapping's key-value pairs into the current mapping, but allows you to override specific keys by defining them locally. Use aliases when you want an exact copy; use merges when you want to inherit and override.
Can anchors cause circular references?
Technically, the YAML specification allows nodes to be referenced multiple times (aliases), but circular references where a node refers back to itself are not valid. Most parsers will detect and reject circular references as parse errors. In practice, circular references do not occur in configuration files - you are always anchoring a block defined earlier in the document and referencing it later.
Are YAML anchors supported in JSON output?
When a YAML file with anchors is parsed, the anchors are resolved into their full values before the data is handed to your application. The resulting in-memory structure has no knowledge of anchors. If you serialize that structure back to JSON, you will get normal JSON with all values fully expanded. Anchors are a YAML authoring convenience; they do not exist in the parsed representation.
Why does my YAML validator say anchors are not supported?
Some strict JSON-schema-based validators process YAML by converting it to JSON first, which resolves anchors but may fail on certain anchor patterns before conversion. If your validator rejects valid anchor syntax, try a dedicated YAML validator that parses the full YAML spec. Our YAML Validator supports anchors, aliases, and the merge key operator.
The Bottom Line
YAML anchors and aliases are one of the most underused features in the YAML specification. They eliminate the copy-paste duplication that makes large configuration files fragile and hard to maintain. The syntax is minimal: &anchor-name to define, *anchor-name to reference, and <<: *anchor-name to merge with overrides.
The pattern pays off immediately in any file with more than two or three duplicated blocks. If you maintain Docker Compose files, Rails database configs, or GitHub Actions workflows, anchors should already be part of your toolbox.
Use our free tool here → YAML Validator to check your anchor syntax, inspect parse errors, and confirm your config expands correctly before committing.
Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic servers, and building zero-knowledge security tools. Read more about the author.