Skip to content
datahacks.dev
YAML

YAML Anchors Aliases and Merge Keys Without Surprises

Define a block once with an anchor, reuse it with an alias, and merge it with the merge key. Real output shows overrides, sharing and where merging stops.

5 min read
Summarize in AI
Flat illustration of a tree of nested objects beside the document it came from, with the YAML logo.

Repetition is the enemy of configuration files. Ten services that share the same logging block, five environments that differ in one key, a list of jobs that all start from the same defaults. YAML has a built-in way to say it once: an anchor gives a node a name, an alias refers to it, and a merge key copies the entries of a mapping into another. The feature works well when you understand its three rules, and badly when a file grows to rely on it. Here are the rules with the output that shows each.

The three pieces

**Anchor, &name.** Written before a node to give it a name. base: &b {host: db, port: 5432} defines the mapping and labels it b.

**Alias, *name.** Refers to the node with that name, anywhere later in the file. dev: *b makes dev the same data as base.

**Merge key, <<.** A special key whose value is a mapping (or a list of mappings) to be merged into the mapping that contains it. <<: *b copies b's entries into this mapping, and keys written locally take precedence.

A complete example with all three:

YAML
base: &b {host: db, port: 5432, opts: {ssl: true}}
prod:
  <<: *b
  host: prod-db
dev: *b

Loaded with PyYAML:

Text
{'base': {'host': 'db', 'port': 5432, 'opts': {'ssl': True}},
 'prod': {'host': 'prod-db', 'port': 5432, 'opts': {'ssl': True}},
 'dev':  {'host': 'db', 'port': 5432, 'opts': {'ssl': True}}}

prod has its own host, prod-db, and inherited port and opts from the anchor. dev is the whole anchored mapping. The base can be a block as well as a flow mapping, and the typical pattern in CI and Compose files puts anchors in an unused top-level key (such as x-defaults:) so the reusable parts are in one place.

Aliases share, they do not copy

An alias is a reference to the same node, and a parser that builds objects keeps one object:

Text
shared object: True | prod opts same object as base: True

In Python, config["prod"]["opts"] is config["base"]["opts"] was True: one dictionary, reachable from two places. That is harmless when you only read the config, and a bug when code modifies it. Setting config["prod"]["opts"]["ssl"] = False in a loop also changes base and every other user of the anchor. If your program mutates loaded configuration, deep-copy it first, or convert to immutable structures. The same sharing happens in memory with the merged copies of nested mappings in many parsers: the merge copies the top-level entries and keeps references to the nested objects.

Aliases also break serialization in the other direction. Dumping that Python structure back to YAML with the same object reachable twice makes PyYAML emit anchors and aliases of its own (&id001, *id001), which surprises people who expected plain data. Use yaml.SafeDumper.ignore_aliases = lambda *args: True to expand everything.

Merge is shallow, and ordering matters

The merge key copies the anchor's top-level keys, and local keys replace whole values:

YAML
prod:
  <<: *b
  opts: {pool: 20}

Here prod.opts is {pool: 20}. It is not {ssl: true, pool: 20}. The local opts replaced the anchor's opts, and ssl is gone. Deep merging is not part of the feature. If you need it, do the merge in code or with a tool that has a deep-merge operator (merging two YAML files in Python with a deep merge and merging YAML files from the command line).

When the value of << is a list of aliases (<<: [*a, *b]), earlier entries win over later ones, which is the opposite of what many people guess. Local keys always win over merged ones.

Anchors must appear before the alias in the document, and they have document scope: one file's anchors are not visible in another, and a multi-document stream resets them at each document.

The merge key was defined in a YAML 1.1 type repository (tag:yaml.org,2002:merge). The YAML 1.2 specification did not carry it forward as part of the core, and its status is unusual: most parsers still support it, the specification does not require it, and the semantics differ in corners. yq ships a flag, --yaml-fix-merge-anchor-to-spec, because its historical behavior differed from the written rules and it planned to change the default. Behavior with nested merges, merge keys in sequences and duplicate keys can differ between libraries.

In practice: <<: *anchor with a single alias is safe in PyYAML, ruamel.yaml, Go's yaml libraries and Ruby, which covers Compose, GitHub Actions-style workflows, GitLab CI and many Kubernetes tools. Exotic uses are where the trouble is. Keep the pattern simple, and test with the parser that will read the file.

Readability

Anchors reduce repetition and increase indirection. A reader who sees <<: *defaults must scroll to find the anchor, remember which keys it defines and mentally apply the override. Three guidelines keep files readable:

  1. Put anchors close to their first use or in one labeled block at the top, and give them names that say what they are (&default_logging, not &a).

  2. Use them for genuine duplication of several keys, not for one repeated value. A value repeated twice is easier to read as written twice.

  3. Do not chain. An anchor that merges another anchor that merges a third is a small inheritance tree that nobody can see.

A linter can help. yamllint has rules about anchors (anchors) that flag unused, undeclared and duplicated anchors, and the key-duplicates rule understands the merge key as well (linting YAML with yamllint).

The cluster hub is YAML syntax you need to stop guessing at. For their use in real config files, see using anchors to reduce repetition in YAML config. Anchors and aliases are specified in section 6.9 of the YAML 1.2.2 specification, and the merge key in the YAML merge type. Expand a file in the YAML to JSON tool to see the result of the merge.

When anchors make a file harder to maintain

The reusable part is small. One repeated value does not need a mechanism.

People edit the file by hand under pressure. An override that silently does not merge deeply is a trap for the person fixing production at night. Prefer explicit repetition for the critical keys.

The consumer expands the file differently. Tools that preprocess YAML (Helm templates, some CI systems) may handle anchors in their own way, or not at all. Test through the full toolchain.

You need to diff changes. A change to an anchored block changes every use, and the diff shows one hunk. Reviewers must know to look for the aliases.

Also inTooling
Share

Keep reading

Get new articles by email

We send one email when a new article is published. No spam.

Unsubscribe in one click. No spam, ever.