Skip to content
Back to skills

Yaml Anchors

ASecurity

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

  • 4 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added June 12, 2026
devopspythongonodedockerkubernetesgitapibackendci/cdsecurity

Works with

  • api

Security analysis

A100/100

Scanned June 12, 2026

npx -y skills add paulpas/agent-skill-router --skill yaml-anchors --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Yaml Anchors?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Yaml Anchors
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/paulpas-yaml-anchors/badge)](https://www.skillsdirectory.com/skills/paulpas-yaml-anchors)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---




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.
   ```yaml
   # 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.
   ```yaml
   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.
   ```yaml
   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.
   ```python
   #!/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

```yaml
# ── 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

```yaml
# ── 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

```yaml
# 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
```yaml
# ❌ 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
```yaml
# ❌ 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
```yaml
# ❌ 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.

- [YAML Spec 1.2.2 — Anchors and Aliases](https://yaml.org/spec/1.2.2/#73-alias-nodes)
- [YAML Spec — Merge Keys (Legacy Type)](https://yaml.org/type/merge.html)
- [PyYAML Documentation](https://pyyaml.org/wiki/PyYAMLDocumentation)
- [ruamel.yaml Documentation](https://sourceforge.net/p/ruamel-yaml/wiki/Home/)
- [Docker Compose File Specification v3.9](https://docs.docker.com/compose/compose-file/compose-file-v3/)
- [Kubernetes YAML Best Practices for Configuration](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/)
- [Helm Values Files and Inheritance](https://helm.sh/docs/chart_best_practices/values/)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.