YAML Cheatsheet

Anchors and Aliases

Use this YAML reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Anchor, alias, and what an alias actually is

&name labels a node. *name refers back to it. An alias is a reference to the same node, not a textual copy and not a deep clone.

defaults: &defaults
  adapter: postgres
  host: localhost
  port: 5432

dev: *defaults
prod: *defaults

parses to:

{"defaults": {"adapter": "postgres", "host": "localhost", "port": 5432},
 "dev":      {"adapter": "postgres", "host": "localhost", "port": 5432},
 "prod":     {"adapter": "postgres", "host": "localhost", "port": 5432}}

In Python, d["dev"] is d["defaults"] is True. Three keys, one object. Mutate one and you mutate all three, which is a genuine surprise the first time a config loader normalises a value in place.

Syntax rules

FormMeaning
&a valueanchor a scalar
&a then indented blockanchor a mapping or sequence
*aalias, the whole node
- &a xanchor a sequence item
k: &a {x: 1}anchor a flow collection

- An anchor name may contain letters, digits, -, and _. No spaces, no [, ], {, }, ,. - The anchor must appear before the alias in document order. A forward reference is found undefined alias. - Anchors are per document. A &base in the first document of a stream is not visible after the next ---. - Never reuse an anchor name. The spec allows redefinition, but PyYAML raises ComposerError: found duplicate anchor 'v' and ruamel emits a ReusedAnchorWarning before shadowing. Portability says one name, one node. - An alias takes the whole node. There is no *base.host syntax and no way to alias one field.

# PyYAML rejects this outright, ruamel warns then shadows
a: &v 1
b: *v      # 1
c: &v 2    # duplicate anchor
d: *v      # 2 under ruamel, hard error under PyYAML

Anchoring scalars

Any node can be anchored, including a single value. This is the cleanest way to keep a repeated version or image tag in one place.

x-version: &version "1.24.3"

services:
  api:
    image: myorg/api:*version        # WRONG, this is a plain string
    version: *version               # right

An alias must be the entire value. You cannot interpolate one into the middle of a string, which is the single most common false expectation about anchors. For string building you need a templating layer (Helm, Kustomize, envsubst), not YAML.

Anchoring sequence items

- &first
  name: a
  role: primary
- *first
- name: b
  role: replica

gives a three-element list where elements 0 and 1 are the same object. Note the anchor sits on the - line with the mapping starting on the next line.

The extension-field convention

An anchor is only useful if the key holding it is either meaningful or ignored. Tools that reject unknown keys need a sanctioned dumping ground.

x-logging: &logging
  driver: json-file
  options:
    max-size: "10m"

services:
  api:
    logging: *logging
  worker:
    logging: *logging

x- prefixed top-level keys are ignored by Docker Compose. GitLab CI uses a leading dot (.job_template:) for the same purpose. Kubernetes has no such convention, which is why anchors are rare in raw manifests.

Where anchors are and are not supported

ToolAnchors and aliases
Docker Composeyes, plus x- extension fields
GitLab CIyes, plus .hidden keys and extends:
Ansibleyes
Kubernetes (kubectl apply)yes, resolved client side by the YAML parser
Helm templatesyes in values.yaml, but templating usually replaces them
GitHub Actionsno, the workflow parser rejects them
CircleCIyes, heavily used

GitHub Actions is the one that catches people. Aliases in a workflow file fail validation, so repetition there has to be handled with reusable workflows or a matrix.

Round-tripping and expansion

- yaml.safe_load resolves aliases. The loaded data has no trace of them. - yaml.dump re-creates them when it sees the same object twice, emitting &id001 / *id001. To suppress that, subclass the dumper:

class NoAliases(yaml.SafeDumper):
    def ignore_aliases(self, data):
        return True

yaml.dump(data, Dumper=NoAliases)

- yq 'explode(.)' expands every alias in place, which is the fastest way to see what a heavily aliased CI config actually says.

yq 'explode(.)' .gitlab-ci.yml

- A self-referencing anchor (a: &a [*a]) is a billion laughs vector. Nested aliases expand multiplicatively, so a 200-byte file can expand to gigabytes. Every hardened parser caps alias expansion. If you accept YAML from users, cap input size and use a parser with an alias budget.