YAML Anchors & Aliases
Reduce duplication in YAML with anchors, aliases, and merge keys.
Anchor and Alias Basics
Define an anchor with & and reference it with * — eliminates duplication
defaults: &defaults
timeout: 30
retries: 3
log_level: info
service_a:
<<: *defaults
port: 8080
service_b:
<<: *defaults
port: 9090
Verify anchor expansion — yq resolves aliases to see the merged result
yq eval 'explode(.)' config.yml
Override a merged value — keys after <<: take precedence
defaults: &defaults
timeout: 30
retries: 3
impatient_service:
<<: *defaults
timeout: 5 # overrides the anchored value
port: 3000
Anchors in Sequences
Anchor a list item and reuse it — works for scalar values
dns_servers:
- &primary_dns 10.50.1.50
- &secondary_dns 10.50.1.51
network_a:
dns:
- *primary_dns
- *secondary_dns
network_b:
dns:
- *primary_dns
Anchor an entire sequence — reuse a whole list
common_ports: &web_ports
- 80
- 443
- 8080
frontend:
allowed_ports: *web_ports
api_gateway:
allowed_ports: *web_ports
Common Infrastructure Patterns
Shared config blocks — DRY environment definitions
ssl_config: &ssl
ssl_enabled: true
ssl_cert: /etc/ssl/certs/server.crt
ssl_key: /etc/ssl/private/server.key
ssl_protocols: "TLSv1.2 TLSv1.3"
web_server:
<<: *ssl
listen: 443
server_name: web.example.com
api_server:
<<: *ssl
listen: 8443
server_name: api.example.com
Environment matrix — base config with per-environment overrides
base: &base
domain: example.com
log_format: json
health_check: /healthz
staging:
<<: *base
replicas: 1
debug: true
production:
<<: *base
replicas: 3
debug: false
Multiple anchors merged — combine several shared blocks
logging: &logging
log_level: info
log_format: json
monitoring: &monitoring
metrics_port: 9090
tracing: true
service:
<<: [*logging, *monitoring]
port: 8080
name: my-service
Merge Key Behavior
Merge does not deep-merge — nested objects are replaced entirely
# This does NOT deep-merge. The entire 'resources' block from &defaults is replaced.
defaults: &defaults
resources:
cpu: 100m
memory: 128Mi
service:
<<: *defaults
resources: # replaces, does not merge with defaults.resources
cpu: 500m
memory: 512Mi
First key wins in merge conflicts — order matters with multiple anchors
a: &a
key: from_a
b: &b
key: from_b
merged:
<<: [*a, *b]
# key will be "from_a" — first anchor wins
Gotchas
Anchors are file-scoped — they do not work across files
# This will NOT work — anchors cannot cross file boundaries
# base.yml defines &defaults, app.yml references *defaults → error
# Solution: use yq to merge files explicitly
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' base.yml app.yml
Detect unresolved aliases — yq will error on missing anchors
yq eval '.' config.yml 2>&1 | grep -i "alias"
Expand all anchors before piping to jq — jq does not understand YAML aliases
yq eval 'explode(.)' config.yml | yq -o=json | jq '.service.timeout'
Antora YAML Anchors
Antora playbook — anchor shared source config for multiple content sources
content:
sources:
- &source_defaults
branches: main
start_path: docs
- url: https://github.com/org/domus-captures.git
<<: *source_defaults
- url: https://github.com/org/domus-infra-ops.git
<<: *source_defaults
antora.yml — anchors for repeated attribute groups are NOT supported
# antora.yml is loaded per-component — anchors defined in one
# component's antora.yml are invisible to others.
# Use Antora's built-in attribute inheritance instead:
# antora-playbook.yml → asciidoc.attributes applies globally
Validation
Validate that anchor expansion produces expected output
# Compare expanded output against expected — catches merge surprises
diff <(yq eval 'explode(.) | .service_a' config.yml) \
<(printf 'timeout: 30\nretries: 3\nlog_level: info\nport: 8080\n')
List all anchors defined in a YAML file
grep -nP '&\w+' config.yml
List all alias references in a YAML file
grep -nP '\*\w+' config.yml