If you've ever copied a block of YAML from one place in a file to another, you've already hit the problem that anchors and aliases solve. Large Kubernetes configs, multi-environment Docker Compose files, and complex CI pipelines tend to repeat the same blocks — resource limits, environment variables, volume mounts — over and over. YAML has a built-in answer for this, and most developers don't know it exists.
Anchors (&) and aliases (*) are YAML's mechanism for defining a value once and
referencing it anywhere else in the same file. Think of an anchor as declaring a variable and an alias as using it.
The merge key (<<) extends this to object merging — similar to JavaScript's spread operator.
Both features are defined in the YAML 1.2 spec section on node anchors.
Basic Anchor and Alias Syntax
The syntax is minimal. Prefix a value with &name to create an anchor,
then use *name anywhere in the document to reference it:
# Define the anchor once
default_timeout: &timeout 30
# Reference it multiple times
services:
api:
timeout: *timeout # → 30
worker:
timeout: *timeout # → 30
scheduler:
timeout: *timeout # → 30When a YAML parser reads this, every alias resolves to the exact same value as the anchor. Change the anchor and all aliases update. No search-and-replace required.
Merging Objects with the << Key
Scalar reuse is useful, but the real power comes from merging entire mapping blocks.
The << merge key absorbs all key-value pairs from the referenced anchor into the current mapping:
# Define a base configuration block
base_service: &base
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
networks:
- app-network
# Merge it into specific services
services:
api:
<<: *base # merge all keys from base_service
image: my-api:latest
ports:
- "8080:8080"
worker:
<<: *base # same base config
image: my-worker:latest
command: ["node", "worker.js"]
scheduler:
<<: *base # and again
image: my-scheduler:latest
command: ["node", "cron.js"]Keys defined directly in the mapping override keys from the merged anchor.
So if base sets restart: unless-stopped and the service also sets
restart: no, the service-level value wins.
Before and After: Docker Compose in the Real World
Here's what a typical multi-service Docker Compose file looks like without anchors — full of repetition:
# BEFORE — repetitive and hard to maintain
version: "3.9"
services:
api:
restart: unless-stopped
env_file: .env
networks: [app-network]
logging:
driver: json-file
options:
max-size: "10m"
depends_on:
- postgres
worker:
restart: unless-stopped
env_file: .env
networks: [app-network]
logging:
driver: json-file
options:
max-size: "10m"
depends_on:
- postgres
mailer:
restart: unless-stopped
env_file: .env
networks: [app-network]
logging:
driver: json-file
options:
max-size: "10m"
depends_on:
- postgresNow with anchors and the merge key — every repeated block is defined exactly once:
# AFTER — DRY and easy to maintain
version: "3.9"
x-common: &common
restart: unless-stopped
env_file: .env
networks: [app-network]
logging:
driver: json-file
options:
max-size: "10m"
depends_on:
- postgres
services:
api:
<<: *common
image: my-api:2.4.1
ports:
- "8080:8080"
worker:
<<: *common
image: my-worker:2.4.1
command: ["node", "worker.js"]
mailer:
<<: *common
image: my-mailer:2.4.1
command: ["node", "mailer.js"]x- prefix on
extension fields
(like x-common) is a Docker Compose convention that prevents Compose from trying to parse the block as a service definition.
It's ignored by the Compose engine but fully usable as a YAML anchor.GitHub Actions: Matrix Strategy with Anchors
CI config is another place where anchors pay off. Here's a GitHub Actions workflow that reuses common step configuration across multiple jobs:
name: Build and Test
on: [push, pull_request]
# Shared setup steps
x-setup-steps: &setup-steps
- 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:
- *setup-steps
- run: npm run lint
test:
runs-on: ubuntu-latest
steps:
- *setup-steps
- run: npm test -- --coverage
build:
runs-on: ubuntu-latest
steps:
- *setup-steps
- run: npm run buildNote: GitHub Actions' YAML parser
does support anchors and aliases. However, some CI systems
(notably CircleCI's config.yml before version 2.1) had their own anchor-like extension syntax.
Always check your specific tool's documentation.
Kubernetes: Reusing Resource Limits
In Kubernetes manifests, resource limits and environment variables are prime candidates for anchors. Here's a pattern I use regularly:
# Anchors at the top of the file (or in a shared ConfigMap generator)
x-resources:
small: &resources-small
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "200m"
large: &resources-large
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "1000m"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
template:
spec:
containers:
- name: frontend
image: frontend:latest
resources: *resources-small # reuse small profile
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
template:
spec:
containers:
- name: api
image: api:latest
resources: *resources-large # reuse large profileMultiple Merges and Override Priority
You can merge from multiple anchors in a single mapping. Provide them as a list under <<.
Earlier entries in the list take priority over later ones for duplicate keys:
defaults: &defaults
color: blue
size: medium
weight: light
overrides: &overrides
color: red # will override defaults.color
weight: heavy # will override defaults.weight
result:
<<: [*overrides, *defaults] # overrides takes priority
name: widget
# Result: color=red, size=medium, weight=heavy, name=widgetLimitations to Know
- Same-file only. Anchors and aliases only work within a single YAML document stream. You cannot reference an anchor from a different file. For cross-file reuse, you need your tooling's own mechanism (e.g., Helm templates, Kustomize, or YAML processors like
ytt). - No computed values. Aliases resolve to exact copies of the anchor's value. There's no interpolation or template logic — that's for tools like Helm or Jinja2.
- Merge key is a YAML extension. The
<<merge key is defined in a YAML 1.1 extension, not the core spec. Most parsers support it, but strictly spec-compliant YAML 1.2 parsers may not. - Circular references break parsers. Don't try to anchor a node and then reference it from within itself. It's technically invalid.
- Aliases copy, not reference. Modifying a resolved alias value in your code does not change the anchor or other aliases. They're independent values after parsing.
Wrapping Up
YAML anchors and aliases are one of the format's most underused features. In any YAML file longer than about 50 lines where the same config block appears more than twice, anchors almost certainly belong there. The before/after difference in maintainability is dramatic — changing one resource limit profile or one shared environment variable instead of hunting through the whole file. Use the YAML Formatter to keep your anchored files tidy, and the YAML Validator to confirm your anchor references resolve correctly.