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