DRY YAML Configs with Anchors, Aliases, and Merge Keys
Learn how YAML anchors, aliases, and merge keys let you reuse configuration blocks, cut duplication, and keep your files maintainable—complete with a Docker Compose example and safety notes.
02 Jan 2026, 17:10 UTC

The Problem: Repeated Blocks in Config Files
When you manage infrastructure or application settings, you often find yourself copying the same block of keys and values across multiple sections. A typical Docker Compose file, for example, might define a base image, environment variables, and restart policy for each service, only to change a port or a command. This duplication makes the file harder to maintain and increases the chance of drift.
How Anchors & Aliases Work
YAML provides two symbols that let you reuse a node without copying its content:
&(anchor) marks a node so it can be referenced later.*(alias) inserts the anchored node’s value at the current location.
When a YAML 1.2‑compliant loader (e.g., PyYAML ≥ 5.1 with SafeLoader) processes the file, the alias resolves to the same data object that the anchor created. If the node is mutable (a list or a map), changes made through one reference appear in all others.
Using Merge Keys to Extend Base Definitions
Sometimes you want to start from an anchored map and add or override a few keys. The merge key syntax << does exactly that:
base: &base
image: myapp:latest
restart: unless-stopped
environment:
- LOG_LEVEL=info
service_a:
<<: *base
ports:
- "8080:80"
service_b:
<<: *base
ports:
- "8081:80"
- "8443:443"
The merged result contains every key from base plus the ports defined locally.
Worked Example: Docker Compose Service Base
Consider a Compose file with three web services that share the same image, restart policy, and logging configuration. Without reuse, the file repeats those lines three times.
Before (≈45 lines)
version: '3.8'
services:
web1:
image: myapp:latest
restart: unless-stopped
environment:
- LOG_LEVEL=info
ports:
- "8080:80"
web2:
image: myapp:latest
restart: unless-stopped
environment:
- LOG_LEVEL=info
ports:
- "8081:80"
web3:
image: myapp:latest
restart: unless-stopped
environment:
- LOG_LEVEL=info
ports:
- "8082:80"
After using anchors and merge keys (≈30 lines)
version: '3.8'
services:
&web_base
image: myapp:latest
restart: unless-stopped
environment:
- LOG_LEVEL=info
web1:
<<: *web_base
ports:
- "8080:80"
web2:
<<: *web_base
ports:
- "8081:80"
web3:
<<: *web_base
ports:
- "8082:80"
The file shrinks by roughly one‑third, and any change to the shared configuration (e.g., updating the image tag) only needs a single edit.
Trade‑offs and Safety Tips
- Readability: Anchors can jump the reader’s eye across the file. Keep anchors close to their aliases or add a short comment explaining the reuse.
- Loader safety: Using
yaml.load()without an explicit loader may instantiate arbitrary Python objects if the YAML contains malicious tags. Always preferSafeLoader(or the equivalent in your language). - Mutability: Because aliases share the same object, mutating a list or map through one alias affects all others. If you need independent copies, restructure the data or deep‑copy after loading.
Putting It Into Practice
To verify that your YAML behaves as expected, run a small Python snippet:
import yaml
yml = """
base: &base
img: myapp:latest
env: ["LOG_LEVEL=info"]
s1:
<<: *base
port: 8080
s2:
<<: *base
port: 8081
"""
data = yaml.load(yml, Loader=yaml.SafeLoader)
print(data['s1']['img'] is data['s2']['img']) # True – same object
print(data['s1']['env'] is data['s2']['env']) # True – same list
# Modifying via one alias shows up in the other
data['s1']['env'].append("DEBUG=1")
print(data['s2']['env']) # ['LOG_LEVEL=info', 'DEBUG=1']
The output confirms reference sharing. If you need independent lists, replace the anchor with a scalar or use a custom constructor that returns a copy.
Start small: pick a repeated block in your current configuration, add an anchor, replace the duplicates with aliases, and run your usual validation (e.g., docker compose config or a schema check). If the file feels clearer and the change propagates correctly, you’ve successfully applied a DRY YAML pattern.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.