yaml-anchors

star 4

Implements YAML anchor and alias patterns (& and *) for configuration reuse, merge keys, and value referencing across Docker Compose, Kubernetes, and Helm files.

paulpas By paulpas schedule Updated 6/4/2026

name: yaml-anchors description: Implements YAML anchor and alias patterns (& and *) for configuration reuse, merge keys, and value referencing across Docker Compose, Kubernetes, and Helm files. license: MIT compatibility: opencode metadata: version: "1.0.0" domain: coding triggers: yaml anchors, yaml aliases, & anchor, * alias, YAML merge key, docker-compose reuse, kubernetes config reuse role: implementation scope: implementation output-format: code content-types: [code, guidance, do-dont, config] related-skills: docker-compose-patterns, kubernetes-configmap, helm-charts archetypes: - tactical - generation anti_triggers: - brainstorming - vague ideation - code golf response_profile: verbosity: low directive_strength: high abstraction_level: operational


YAML Anchor and Alias Patterns

Implements YAML anchor (&name) and alias (*name) patterns — along with merge keys (<<:) — to eliminate configuration duplication in Docker Compose, Kubernetes manifests, Helm templates, and CI/CD pipeline definitions. When loaded, this skill makes the model design, refactor, and validate YAML documents that use structured composition instead of copy-pasted blocks.

TL;DR Checklist

  • Define anchors at a logical document level with descriptive snake_case names (&base_service_config, not &x)
  • Use merge key <<: *anchorName to shallow-merge anchor mappings into child configs
  • Verify no circular references exist between anchors and aliases
  • Test anchor behavior under YAML 1.2 parsers (PyYAML >= 6.0, ruamel.yaml)
  • Comment complex multi-anchor merge chains for readability

When to Use

Use this skill when:

  • Reducing duplication in Docker Compose, Kubernetes, or Helm configuration files where the same settings repeat across multiple services or environments
  • Sharing common settings across many resources (e.g., logging drivers, health check configs, network policies) with only minor per-service overrides
  • Building template-based deployments where dozens of resources share a base configuration pattern (Kubernetes Deployments, Helm values inheritance)
  • Implementing configuration inheritance in YAML-based CI/CD pipelines (GitHub Actions reusable workflows, GitLab CI includes)
  • Consolidating environment-specific configurations where staging and production differ only in replica counts, resource limits, or feature flags

When NOT to Use

Avoid this skill for:

  • Simple key-value pairs that don't justify the cognitive overhead of anchors (a single repeated port number should be a variable, not an anchor)
  • Files with fewer than 3 duplicative blocks — anchors add indirection; if duplication appears only twice, copy-paste is clearer
  • YAML 1.0 parsers where the merge key (<<:) behavior is undefined and inconsistent across implementations
  • Deeply nested object graphs (3+ levels of anchor references within anchored nodes) where scoping becomes impossible to trace without a debugger

Core Workflow

  1. Identify Duplicative Patterns — Scan the entire YAML document for repeated configuration blocks. Look for: identical logging drivers, shared health check definitions, duplicate network configurations, or repeated deploy/resource specifications. Use structural comparison (not just visual) to catch variations. Checkpoint: If more than 2 structurally identical blocks exist with only minor field differences, proceed to create an anchor. Count the exact lines of duplication — if total saved exceeds 10 lines, anchors are justified.

  2. Define Anchor with Descriptive Name — Place &anchorName on the mapping or scalar node being reused. Use descriptive snake_case names that reflect the semantic content, not its position in the file. Anchors must be unique within the document scope.

    # Define anchors at a logical structural level, not nested inside another key's value
    _base_service_config: &base_service_config
      restart_policy: unless-stopped
      deploy:
        replicas: 3
        resources:
          limits:
            memory: 512M
            cpus: "0.5"
      healthcheck:
        test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
        interval: 30s
        timeout: 10s
        retries: 3
        start_period: 40s
      logging:
        driver: json-file
        options:
          max-size: "10m"
          max-file: "3"
    

    Checkpoint: Anchor name must be unique within the document — re-anchoring the same name silently overwrites previous definitions. Use a prefix like _base_ to signal shared definition at a glance.

  3. Apply Alias or Merge Key — For simple scalar value reuse, reference with *anchorName as a direct replacement node. For mapping inheritance, use <<: *anchorName merge key to shallow-merge all keys from the anchored mapping into the current context. Keys listed after the merge key override corresponding keys from the anchor.

    services:
      api-server:
        <<: *base_service_config    # merges ALL keys from &base_service_config
        hostname: api-prod          # adds new key not present in anchor
        ports:
          - "8080:8080"             # new key — fully replaces if same name exists
        deploy:                     # nested object — ENTIRE mapping is REPLACED, not deep-merged
          replicas: 5               # override from within the new deploy mapping
    
      worker:
        <<: *base_service_config
        hostname: worker-prod
        command: ["python", "worker.py"]
        deploy:
          replicas: 3
    

    Checkpoint: Merge keys perform SHALLOW merge only — nested objects are entirely replaced, not recursively merged. If you need to override a nested property, re-define the entire parent mapping in the child.

  4. Override and Extend Per Service — For each child configuration that references an anchor, specify ONLY the keys that differ from the base. Document intentional overrides with inline comments explaining why the value was changed.

    services:
      api-server:
        <<: *base_service_config
        hostname: api-prod
        # Override replicas for high-traffic API service
        deploy:
          replicas: 5
          resources:
            limits:
              memory: 1G            # API needs more memory
              cpus: "1.0"
        ports:
          - "8080:8080"
    
      worker:
        <<: *base_service_config
        hostname: worker-prod
        # Workers run a different command but share the same infra settings
        command: ["python", "-m", "worker.main"]
    

    Checkpoint: Each override should be justified by a comment. If a child has no overrides at all, consider if it even needs its own entry or could use the anchor directly.

  5. Validate Under YAML 1.2 Parser — Test file resolution with modern parsers before committing to CI/CD pipelines. Many cloud-native tools now ship YAML 1.2 parsers (PyYAML >= 6.0 switched defaults) which are stricter about merge key behavior and scalar types.

    #!/usr/bin/env python3
    """Validate YAML anchor resolution across the configuration file."""
    
    import sys
    from pathlib import Path
    
    import yaml
    
    def validate_yaml_anchors(filepath: str) -> dict:
        """Parse a YAML file and verify all anchors resolved correctly.
    
        Returns a summary dict with success status and any errors found.
        """
        try:
            with open(filepath, "r") as f:
                config = yaml.safe_load(f)
        except yaml.YAMLError as exc:
            return {"success": False, "error": str(exc)}
    
        if not isinstance(config, dict):
            return {
                "success": False,
                "error": f"Expected top-level mapping, got {type(config).__name__}",
            }
    
        # Verify known anchor-merged structures are intact
        errors = []
        services = config.get("services", {})
        if not isinstance(services, dict):
            errors.append("'services' is missing or not a mapping")
            return {"success": False, "errors": errors}
    
        for svc_name, svc_config in services.items():
            restart = svc_config.get("restart_policy")
            if restart != "unless-stopped":
                errors.append(
                    f"Service '{svc_name}' missing inherited 'restart_policy'"
                )
            deploy = svc_config.get("deploy", {})
            if not isinstance(deploy, dict):
                errors.append(
                    f"Service '{svc_name}' deploy is not a mapping — "
                    f"anchor merge may have failed"
                )
    
        return {
            "success": len(errors) == 0,
            "errors": errors,
            "service_count": len(services),
        }
    
    if __name__ == "__main__":
        path = sys.argv[1] if len(sys.argv) > 1 else "docker-compose.yaml"
        result = validate_yaml_anchors(path)
        print(f"Anchors valid: {result['success']}")
        if result.get("errors"):
            for err in result["errors"]:
                print(f"  ERROR: {err}")
        else:
            print(f"  Validated {result['service_count']} services successfully")
    

    Checkpoint: Parse the file with both yaml.safe_load() and ruamel.yaml.RoundTripLoader to catch version-specific behavior differences. If either parser fails, the anchors are non-portable.


Implementation Patterns

Pattern 1: Docker Compose Service Inheritance

# ── Base service definition (anchor for all services) ──
_base_service: &base_service_definition
  image: ${REGISTRY:-ghcr.io}/myorg/myapp:${TAG:-latest}
  restart_policy: unless-stopped
  healthcheck:
    test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
    interval: 30s
    timeout: 10s
    retries: 3
    start_period: 40s
  logging:
    driver: json-file
    options:
      max-size: "10m"
      max-file: "3"

# ── Production service overrides ──
services:
  api-server:
    <<: *base_service_definition
    hostname: api-prod
    ports:
      - "443:8080"
    deploy:
      replicas: 5
      resources:
        limits:
          memory: 1G
          cpus: "1.0"
    environment:
      NODE_ENV: production
      LOG_LEVEL: warn

  worker:
    <<: *base_service_definition
    hostname: worker-prod
    command: ["python", "-m", "celery.worker"]
    deploy:
      replicas: 3
      resources:
        limits:
          memory: 2G
          cpus: "2.0"

  scheduler:
    <<: *base_service_definition
    hostname: scheduler-prod
    command: ["python", "-m", "celery.beat"]

Pattern 2: Kubernetes ConfigMap + Deployment Reuse

# ── Shared container spec anchor ──
_base_container: &base_container
  imagePullPolicy: IfNotPresent
  securityContext:
    runAsNonRoot: true
    readOnlyRootFilesystem: true
    allowPrivilegeEscalation: false
  resources:
    requests:
      memory: "128Mi"
      cpu: "100m"
    limits:
      memory: "512Mi"
      cpu: "500m"
  envFrom:
    - configMapRef:
        name: app-config

# ── Deployment with shared container template ──
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-api
spec:
  replicas: 3
  template:
    spec:
      containers:
        - <<: *base_container
          name: api
          ports:
            - containerPort: 8080
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 20
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-worker
spec:
  replicas: 2
  template:
    spec:
      containers:
        - <<: *base_container
          name: worker
          command: ["python", "-m", "celery.worker"]
          # Resources from anchor are inherited; overrides below:
          resources:
            requests:
              memory: "256Mi"
              cpu: "200m"
            limits:
              memory: "1Gi"
              cpu: "1000m"

Pattern 3: Helm Values Inheritance with Anchors and Merge Keys

# values.yaml — demonstrate anchor usage in Helm charts
base_ingress: &base_ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
  tls:
    - secretName: app-tls
      hosts:
        - example.com
  rules:
    - host: example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  number: 80

# Environment-specific overrides via merge key
staging:
  ingress:
    <<: *base_ingress
    annotations:
      # Override annotation only — other keys inherited from anchor
      nginx.ingress.kubernetes.io/ssl-redirect: "false"
    rules:
      - host: staging.example.com

production:
  ingress:
    <<: *base_ingress
    tls:
      - secretName: prod-tls
        hosts:
          - example.com
          - api.example.com

Constraints

MUST DO

  • Use descriptive anchor names that reflect the content they contain (&base_service_config, not &x, &1, or &temp)
  • Test YAML files with both PyYAML >= 6.0 and ruamel.yaml before committing to CI/CD pipelines to catch parser differences
  • Place anchors at a logical document level — define them where they make semantic sense, typically near the top of the file
  • Document complex multi-anchor merge chains with inline comments explaining which anchor each service inherits from and what it overrides

MUST NOT DO

  • Reuse the same anchor name (&defaults) in the same file — it silently overwrites previous definitions without warning
  • Expect merge keys (<<:) to deep-merge nested objects — they perform shallow merge only; entire nested mappings are fully replaced by child keys of the same name
  • Anchor inside a sequence item and expect to reference just that item — anchoring on a list anchors the entire list node
  • Use &anchor and *alias in YAML 1.0 parsers where merge key behavior is undefined

Common Pitfalls

Circular References

# ❌ CRASHES — YAML parser detects circular reference and raises an error
circular_a: &a
  self_ref: *a              # Fatal error: recursive mapping definition detected

parent: &parent_config
  children_count: 3
  child_names: [alice, bob, charlie]
# ✅ GOOD — Always verify no anchor references itself or creates a cycle

Shallow Merge Replaces Nested Objects

# ❌ BAD — "format" key is silently lost because the entire logging mapping is replaced
defaults: &defaults
  logging:
    level: info
    format: json

app_config:
  <<: *defaults
  logging:
    level: debug          # ⚠️ "format: json" is completely lost!
                          # The parser replaces the whole 'logging' mapping

# ✅ GOOD — Re-anchor the nested object for targeted deep override
defaults: &defaults
  logging:
    level: info
    format: json

logging_debug_override: &logging_debug
  <<: *defaults.logging   # re-anchor only the nested mapping
  level: debug            # now only "level" is overridden, "format" preserved

app_config:
  <<: *defaults
  logging: *logging_debug

Scalar Anchor Misuse

# ❌ BAD — Anchoring a scalar value does NOT perform string interpolation
_port_value: &api_port 8080

services:
  web:
    ports:
      - "*api_port:80"       # ⚠️ This is a literal string "*api_port:80", not expanded!
                             # Anchors on scalars replace the ENTIRE node, not substrings

# ✅ GOOD — Use environment variables for values that need interpolation in strings
environment:
  API_PORT: "8080"

services:
  web:
    ports:
      - "${API_PORT}:80"     # Docker Compose resolves this correctly

Output Template

When implementing YAML anchor patterns, produce:

  1. Identified anchors — List each duplicative block found and its proposed anchor name with justification
  2. Anchor definitions — Show the full &anchorName definition including every key-value pair being reused
  3. Alias/merge usage — Show each *anchorName or <<: *anchorName reference with its override keys clearly commented
  4. Override documentation — For each child config, document what differs from the anchor and why
  5. Parser validation results — Confirm the file loads correctly under both PyYAML 6.0+ and ruamel.yaml, noting any version-specific differences

Live References

Authoritative documentation for YAML anchoring and merging patterns.

Install via CLI
npx skills add https://github.com/paulpas/agent-skill-router --skill yaml-anchors
Repository Details
star Stars 4
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator