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: *defaultsparses 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
| Form | Meaning |
|---|---|
&a value | anchor a scalar |
&a then indented block | anchor a mapping or sequence |
*a | alias, the whole node |
- &a x | anchor 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: *loggingx- 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
| Tool | Anchors and aliases |
|---|---|
| Docker Compose | yes, plus x- extension fields |
| GitLab CI | yes, plus .hidden keys and extends: |
| Ansible | yes |
Kubernetes (kubectl apply) | yes, resolved client side by the YAML parser |
| Helm templates | yes in values.yaml, but templating usually replaces them |
| GitHub Actions | no, the workflow parser rejects them |
| CircleCI | yes, 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.