YAML Anchors and Aliases: DRY Config That Might Not Parse
Reuse YAML nodes with anchors and aliases, but know where merge keys and shared references can break your pipeline.
28 Jul 2025, 21:55 UTC

Start with the repetition problem
You maintain a CI configuration with three jobs that all need the same Docker image, cache paths, and environment variables. You copy the block three times. Six months later, someone updates the image in one job but forgets the others. The pipeline passes in staging and fails in production.
YAML offers anchors and aliases to label a node and reuse it elsewhere. An anchor marks a node with &id. An alias pulls that same node back in with *id. This keeps related configuration in one place, but it is not without traps.
Anchors and aliases in practice
Here is a configuration fragment for a CI file. The anchor &base-image labels a mapping. Two jobs reference it with *base-image.
base-image: &base-image
image: node:20-alpine
cache:
paths:
- node_modules
build-job: *base-image
test-job: *base-image
This works in any parser that supports anchors and aliases, which is nearly all of them. It avoids the merge key syntax <<: *id, which is a popular extension but not part of the core YAML 1.1 or YAML 1.2 specification. A strict parser will reject a document that relies on merge keys.
Shared references can surprise you
Aliases point back to the same source node. After parsing, some libraries return the same in-memory object for every alias. If your application mutates the parsed structure—adding a key to build-job, for example—the change can appear in test-job too. Whether this happens depends on the library, not the YAML file.
If you need independent copies, deep-clone the aliased structures before modifying them. Or avoid aliases for objects your code will mutate.
Validate before you rely on it
Run this check locally. You need Python 3 and PyYAML installed. No administrator permissions are required.
python3 - <<'PY'
import yaml
path = 'ci-config.yaml'
with open(path, 'r', encoding='utf-8') as handle:
data = yaml.safe_load(handle)
print('build-job:', data.get('build-job'))
print('test-job:', data.get('test-job'))
print('same object:', data.get('build-job') is data.get('test-job'))
PY
Where to run: your terminal in the directory containing ci-config.yaml.
Required permissions: read access to the file.
Expected checks: the command prints both jobs without a parser error. The same object line tells you whether your library shares references between aliases.
Relevant risks: yaml.safe_load blocks custom tags such as !MyObject, which is what you want for untrusted files. It does not protect against deeply nested or very large documents that can exhaust memory or CPU.
Trade-offs and when to avoid merge keys
| Approach | Portable? | Best for |
|---|---|---|
| Anchor plus alias on a scalar or mapping | Yes | Reusing images, environment maps, or defaults |
Merge key <<: *id |
No | Internal tools only, after parser verification |
| Repeated blocks | Yes | Small configs where clarity beats DRY |
YAML 1.1 also treats unquoted yes, no, on, and off as booleans. YAML 1.2 recognizes only true and false. If your config contains a value like yes as a version string or environment name, check which YAML version your parser implements.
Closing checklist
- Use
&idand*idto eliminate repeated blocks in CI, Kubernetes, or application configs. - Test merge keys with every parser in your pipeline before committing them.
- Assume aliases may share in-memory references; clone before mutating.
- Load untrusted YAML with a safe loader such as
yaml.safe_load. - Verify with a small parse script like the one above, and confirm aliased values are identical after loading.
Anchors and aliases are worth using when repetition creates drift. Merge keys are worth avoiding unless you have confirmed your parsers support them.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.