Removing Duplication in YAML with Anchors, Aliases, and Merge Keys
YAML anchors and merge keys let you define a shared mapping once and override it per service. This guide covers prerequisites, a focused procedure, verification steps, and what to do when a parser lacks merge-key support.
09 Jun 2026, 22:57 UTC

The duplication problem and the payoff
Configuration files for multi-service deployments often repeat the same mapping—resource limits, adapter settings, or environment defaults—across several services. When a shared value changes, every copy must be edited consistently; missing one causes drift that is hard to spot in review. YAML anchors (&name) and aliases (*name) let you define a value once and reference it elsewhere. The merge key (<<) extends this to mappings, allowing a service to inherit a shared base and override only the fields that differ.
Prerequisites: parser and schema support
Before editing a live configuration file, confirm that every consumer of that YAML supports the features you plan to use.
- Parser merge-key support. The YAML 1.2 core schema does not define
<<; it is a YAML 1.1-era convention. Some strict 1.2 parsers treat it as an ordinary key or reject it. Check the documentation for the exact library and version used in production. - Schema validation timing. If a JSON-schema validator reads the raw file rather than the parsed object, it may see
<<as an unknown property. Validate the resolved output instead. - Document boundaries. Anchors are scoped to a single document. An anchor defined in one
---document cannot be referenced in another. - Toolchain stability. Formatters and linters may reorder or rewrite keys. If a tool does not preserve
<<placement, merge semantics can change silently.
Procedure: anchor, alias, and merge
The pattern has three parts: define a canonical mapping with an anchor, reference it inside each override mapping, and place explicit keys after the merge key so they take precedence.
Consider a service configuration where several environments share database adapter settings but differ in host, database name, and timeout:
_defaults: &db_defaults
adapter: postgres
host: localhost
port: 5432
timeout: 30
development:
<<: *db_defaults
database: dev_db
staging:
<<: *db_defaults
database: staging_db
host: db.staging.internal
production:
<<: *db_defaults
database: prod_db
host: db.prod.internal
timeout: 60
Here, &db_defaults marks the shared mapping. Each environment uses <<: *db_defaults to inherit adapter, host, port, and timeout. The keys written after the merge line—database, host, and timeout—override the inherited values. In many common parsers, explicit keys win over merged keys, but this behavior is implementation-specific; verify it with your exact library rather than assuming it.
Keep anchors near the top of the document so readers encounter the definition before the references. Avoid deeply nested or cyclic alias graphs: some libraries produce confusing errors or consume excessive memory when resolving them.
Expected checks: prove the merge happened
An anchor that is silently ignored produces a configuration that looks correct but is missing inherited values. Run these checks before deploying.
Parse and inspect the resolved object
Load the file with the same library and version your application uses, then print or serialize the result. For example, if your service uses Python with PyYAML, run this in the directory containing the file (no special permissions are required):
python3 -c "import yaml, json; data = yaml.safe_load(open('config.yaml')); print(json.dumps(data, indent=2))"
Confirm that development, staging, and production each contain the merged keys (adapter, port) plus their overrides. The output should show no unresolved alias nodes.
Diff against a hand-expanded reference
Create a second file with the shared block written out literally in each environment, then compare the parsed structures. They should be semantically identical. A quick way to do this is to serialize both parsed objects to JSON and diff the output:
python3 -c "import yaml, json; a = yaml.safe_load(open('config.yaml')); b = yaml.safe_load(open('config-expanded.yaml')); print(json.dumps(a, sort_keys=True) == json.dumps(b, sort_keys=True))"
If the result is False, the merge is not producing the structure you expect.
Negative test: remove the anchor
Temporarily delete the anchor definition (the &db_defaults label) and re-run the parse. If the file still loads without error, your parser is ignoring the alias rather than resolving it, and the configuration is not safe to deploy. Restore the anchor immediately after this check.
Validate the resolved output
Run your schema validator against the parsed object, not the raw file. This catches cases where a validator would otherwise flag << as an unknown key.
Recovery when merge keys are rejected
If a downstream consumer rejects << or aliases, you have two practical options.
Expand the shared block literally. Copy the canonical mapping into each location and remove the anchor and alias. This is the safest fallback because it works with any YAML 1.2 parser, but it reintroduces the duplication you were trying to remove.
Preprocess before deployment. Keep the anchored source file as the single source of truth, then run a script that resolves anchors and merge keys and writes a plain YAML file for the consumer. Validate the generated file with the same schema and checks described above. This keeps the authoring experience DRY while producing a compatible artifact.
If an anchor is referenced across a --- document boundary, move the anchor definition into the same document as its aliases, or duplicate the shared block. Anchors do not cross document boundaries, and attempting to do so will fail parsing.
Deciding what to anchor
Anchors create hidden coupling: a change to the shared block affects every location that references it. Use them for values that must stay identical across services—image tags, security contexts, or resource limits that are governed by a single policy. Avoid them for values that drift independently, such as per-service replica counts or environment-specific hostnames, because the indirection reduces reviewability. When a value is likely to diverge, write it explicitly even if it means repeating a line or two.
A useful rule of thumb: if a reviewer would need to open the anchor definition to understand whether a change is safe, the coupling is probably too tight.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.