DevToolBoxฟรี
บล็อก

YAML Anchors, Aliases และ Merge Keys

8 นาทีในการอ่านโดย DevToolBox

YAML anchors and aliases let you define a value once and reuse it throughout your document, following the DRY (Don't Repeat Yourself) principle. Combined with merge keys (<<), you can build composable, maintainable configuration files for Docker Compose, GitHub Actions, GitLab CI, Kubernetes, and more. This guide covers everything from basic syntax to advanced patterns and common pitfalls.

Try our JSON to YAML Converter|Validate your YAML online

1. What Are Anchors & Aliases?

In YAML, an anchor is a marker you attach to a node (scalar, mapping, or sequence) using the & character followed by a name. An alias is a reference to that anchor using the * character followed by the same name. When the YAML parser encounters an alias, it substitutes the anchored value in place.

This mechanism is part of the YAML 1.1 and 1.2 specifications, meaning it works with any compliant parser including PyYAML, js-yaml, SnakeYAML, ruamel.yaml, and go-yaml.

Anchors and aliases are the native YAML way to avoid repeating yourself. Instead of copying the same block of configuration multiple times, you define it once and reference it everywhere else.

# Anchor: &name attaches a label to a value
# Alias: *name references that labeled value

defaults: &default_settings
  timeout: 30
  retries: 3
  verbose: false

# Alias: reuse the entire defaults block
production:
  <<: *default_settings
  verbose: true

# After parsing, production = { timeout: 30, retries: 3, verbose: true }
  • &name creates an anchor on a node
  • *name creates an alias (reference) to that anchor
  • Anchors must be defined before they are referenced
  • Anchor names can contain alphanumeric characters, hyphens, and underscores
  • Aliases produce an identical copy of the anchored node

2. Basic Anchor & Alias Syntax

The simplest use of anchors and aliases is with scalar values (strings, numbers, booleans). You attach an anchor to a value and then reference it elsewhere in the same document.

Scalar anchors:

# Define a scalar value with an anchor
db_host: &db_host "postgres.example.com"
db_port: &db_port 5432
db_name: &db_name "myapp_production"

# Reference with aliases
connection_string: "postgresql://user:pass@*db_host:*db_port/*db_name"
# Note: aliases work as standalone values, not inside strings!
# Correct usage:
primary:
  host: *db_host
  port: *db_port
  name: *db_name

backup:
  host: *db_host    # Same host as primary
  port: *db_port    # Same port as primary
  name: *db_name    # Same database name
This is useful when you have a value like a database host, port, or version number that appears in multiple places.

After parsing, db_host in both connection_string and backup_host resolves to "postgres.example.com".

Multiple anchors in the same document:

# Multiple anchors for different values
app_version: &version "2.5.0"
node_image: &node_img "node:20-alpine"
python_image: &python_img "python:3.12-slim"
default_replicas: &replicas 3

services:
  api:
    image: *node_img
    replicas: *replicas
    labels:
      version: *version

  worker:
    image: *python_img
    replicas: *replicas
    labels:
      version: *version

  frontend:
    image: *node_img
    replicas: 1           # Override: only 1 replica for frontend
    labels:
      version: *version

3. Anchoring Objects (Mappings)

The real power of anchors comes from anchoring entire mappings (objects). You can define a complete configuration block once and reuse it across your document.

When you anchor a mapping, the alias produces a complete copy of every key-value pair in that mapping.

# Anchor an entire mapping (object)
logging: &default_logging
  driver: json-file
  options:
    max-size: "10m"
    max-file: "3"
    tag: "{{.Name}}"

services:
  api_server:
    image: myapp-api:latest
    logging: *default_logging    # Entire logging config reused

  worker_server:
    image: myapp-worker:latest
    logging: *default_logging    # Same logging config

  scheduler:
    image: myapp-scheduler:latest
    logging: *default_logging    # Same logging config
Both api_server and worker_server will have the identical logging configuration. If you change the anchor, all aliases update automatically.

Anchoring nested objects:

# Anchor nested configuration blocks
database_config: &db_config
  host: db.internal.example.com
  port: 5432
  pool_size: 20
  ssl: true
  timeout: 30

cache_config: &cache_config
  host: redis.internal.example.com
  port: 6379
  ttl: 3600

environments:
  production:
    database: *db_config
    cache: *cache_config

  staging:
    database: *db_config      # Exact same DB config
    cache: *cache_config      # Exact same cache config

  # For different config, you'd need merge keys (section 4)
  # or define a new block
The staging environment gets the exact same database configuration as production. This ensures consistency across environments.

4. Merge Key (<<): Merging Mappings

The merge key (<<) is a YAML extension that lets you merge an anchored mapping into another mapping while allowing you to override specific fields. This is far more useful than plain aliases for configuration files.

With <<: *alias, all keys from the anchored mapping are inserted into the current mapping. If the current mapping already defines a key that exists in the anchor, the local value wins (local takes precedence).

# Define defaults with an anchor
defaults: &service_defaults
  image: myapp:latest
  restart: always
  environment:
    NODE_ENV: production
    LOG_LEVEL: info
  volumes:
    - /var/log/app:/app/logs
  ports:
    - "8080:3000"

services:
  production:
    <<: *service_defaults       # Merge all defaults
    # production uses everything as-is

  staging:
    <<: *service_defaults       # Merge all defaults
    ports:
      - "8081:3000"             # Override: different port
    environment:
      NODE_ENV: staging         # Override: different NODE_ENV
      LOG_LEVEL: debug          # Override: more verbose logging

  development:
    <<: *service_defaults       # Merge all defaults
    image: myapp:dev            # Override: dev image
    ports:
      - "3000:3000"             # Override: direct port mapping
    environment:
      NODE_ENV: development
      LOG_LEVEL: debug
      DEBUG: "true"
The staging service inherits image, environment, and volumes from the defaults, but overrides the ports to use 8081 instead of 8080.

Override precedence:

Local keys always take precedence over merged keys. This is the fundamental rule of merge keys.

# Override precedence demonstration
base: &base
  name: "default"
  timeout: 30
  retries: 3
  debug: false

service:
  <<: *base
  name: "my-service"    # Overrides "default" -> "my-service"
  debug: true           # Overrides false -> true
  # timeout: 30         <- inherited from base (not overridden)
  # retries: 3          <- inherited from base (not overridden)
  extra_key: "new"      # Added: not in base at all

# Parsed result:
# service:
#   name: "my-service"
#   timeout: 30
#   retries: 3
#   debug: true
#   extra_key: "new"

5. Multiple Merges & Precedence

You can merge from multiple anchors at once by passing a list to the << key. When merging multiple anchors, the first anchor in the list has the highest precedence among the merged values, and local keys still override everything.

Precedence order (highest to lowest):

  • 1. Local keys defined in the current mapping
  • 2. First anchor in the merge list
  • 3. Second anchor in the merge list
  • 4. Third anchor in the merge list, and so on
# Multiple merge sources
app_defaults: &app_defaults
  image: myapp:latest
  restart: always
  replicas: 3

logging_defaults: &logging_defaults
  logging:
    driver: json-file
    options:
      max-size: "10m"

monitoring_defaults: &monitoring_defaults
  labels:
    monitoring: "true"
    team: "platform"
  healthcheck:
    interval: 30s
    timeout: 10s
    retries: 3

services:
  api:
    # Merge from multiple anchors (list syntax)
    <<: [*app_defaults, *logging_defaults, *monitoring_defaults]
    ports:
      - "8080:3000"

  worker:
    <<: [*app_defaults, *logging_defaults, *monitoring_defaults]
    replicas: 5              # Override: more replicas for worker
    command: ["npm", "run", "worker"]

  # If app_defaults and monitoring_defaults both define "labels",
  # app_defaults wins (first in the list)
# Precedence example with conflicting keys
first: &first
  color: red
  size: large
  weight: heavy

second: &second
  color: blue
  size: medium
  shape: round

result:
  <<: [*first, *second]
  color: green             # Local override

# Parsed result:
# result:
#   color: green           <- local key wins
#   size: large            <- from *first (first in list)
#   weight: heavy          <- from *first (only source)
#   shape: round           <- from *second (only source)
In the production service, the port comes from the local definition (9090), the image comes from the first merge (*app_defaults), and any remaining keys come from subsequent merges.

6. Anchoring Arrays (Sequences)

You can anchor entire arrays (sequences) just like scalars and mappings. However, there is an important limitation: you cannot merge arrays the way you merge mappings with <<.

Array anchors work with simple aliasing:

# Anchor an entire array
shared_volumes: &volumes
  - ./config:/app/config:ro
  - ./logs:/app/logs
  - /var/run/docker.sock:/var/run/docker.sock

shared_ports: &ports
  - "8080:3000"
  - "8443:3443"

services:
  web:
    volumes: *volumes      # Reuse entire volume list
    ports: *ports          # Reuse entire port list

  api:
    volumes: *volumes      # Same volumes
    ports:
      - "9090:3000"        # Different ports (cannot merge with *ports)
Limitation: There is no native "array merge" in YAML. You cannot use << with sequences to combine two lists. If you need to extend an array, you must repeat the values or use a tool-specific feature.

Workaround for extending arrays:

# YAML does NOT support this (will cause an error):
# combined:
#   <<: [*list_a, *list_b]   # ERROR: << only works with mappings

# Workaround 1: Repeat values manually
all_hosts:
  - host1.example.com
  - host2.example.com
  - host3.example.com        # Additional host
  - host4.example.com        # Additional host

# Workaround 2: Use a mapping with anchor + merge instead
host_group_a: &hosts_a
  host1: host1.example.com
  host2: host2.example.com

host_group_b: &hosts_b
  host3: host3.example.com
  host4: host4.example.com

all_hosts:
  <<: [*hosts_a, *hosts_b]
  # Result: { host1: ..., host2: ..., host3: ..., host4: ... }
Some tools like Docker Compose handle array merging in their own way, but this is tool-specific behavior, not standard YAML.

7. Docker Compose: x- Extension Fields

Docker Compose is the most popular real-world use case for YAML anchors. Starting with Compose file format 3.4+, you can use x- prefixed top-level keys as extension fields to hold your anchor definitions. Docker Compose ignores any top-level key starting with x-.

This pattern is the recommended way to share configuration across services:

# docker-compose.yml
# x- extension fields are ignored by Docker Compose
x-default-service: &default-service
  restart: unless-stopped
  logging:
    driver: json-file
    options:
      max-size: "10m"
      max-file: "3"
  networks:
    - app-network
  deploy:
    resources:
      limits:
        memory: 512M
      reservations:
        memory: 256M

services:
  api:
    <<: *default-service
    image: myapp-api:latest
    ports:
      - "8080:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://db:5432/myapp
    depends_on:
      - postgres
      - redis

  worker:
    <<: *default-service
    image: myapp-worker:latest
    environment:
      - NODE_ENV=production
      - QUEUE_URL=redis://redis:6379
    depends_on:
      - redis

  scheduler:
    <<: *default-service
    image: myapp-scheduler:latest
    environment:
      - NODE_ENV=production
    deploy:
      resources:
        limits:
          memory: 256M        # Less memory for scheduler
        reservations:
          memory: 128M

  postgres:
    <<: *default-service
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=app
      - POSTGRES_PASSWORD_FILE=/run/secrets/db_password

  redis:
    <<: *default-service
    image: redis:7-alpine
    volumes:
      - redisdata:/data

networks:
  app-network:
    driver: bridge

volumes:
  pgdata:
  redisdata:
The x-defaults key is ignored by Docker Compose but serves as a home for your anchor. Each service merges the defaults and overrides what it needs.

Advanced Docker Compose with multiple anchors:

# Advanced: Multiple x- extension fields for composability
x-logging: &logging
  logging:
    driver: json-file
    options:
      max-size: "10m"
      max-file: "5"

x-healthcheck-http: &healthcheck-http
  healthcheck:
    test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
    interval: 30s
    timeout: 10s
    retries: 3
    start_period: 40s

x-healthcheck-tcp: &healthcheck-tcp
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -U app || exit 1"]
    interval: 10s
    timeout: 5s
    retries: 5

x-deploy-standard: &deploy-standard
  deploy:
    replicas: 2
    resources:
      limits:
        cpus: "1.0"
        memory: 512M

services:
  api:
    <<: [*logging, *healthcheck-http, *deploy-standard]
    image: myapp-api:latest
    ports:
      - "8080:3000"

  worker:
    <<: [*logging, *deploy-standard]
    image: myapp-worker:latest
    deploy:
      replicas: 4            # More workers needed

  postgres:
    <<: [*logging, *healthcheck-tcp]
    image: postgres:16-alpine
By combining multiple x- extension fields, you can build a composable configuration where services pick and choose which defaults they inherit.

8. GitHub Actions: Reusable Steps with Anchors

GitHub Actions YAML workflows support anchors and aliases, though with some caveats. Anchors work within a single workflow file but not across files.

Common patterns include reusing environment variables, step configurations, and matrix definitions:

# .github/workflows/ci.yml
name: CI Pipeline

# Define reusable environment variables
env: &shared-env
  NODE_VERSION: "20"
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    env: *shared-env
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: npm

      - &install-deps
        name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint

      - name: Test
        run: npm test -- --coverage

  build:
    runs-on: ubuntu-latest
    needs: lint-and-test
    env: *shared-env
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: npm

      - *install-deps    # Reuse the install step

      - name: Build
        run: npm run build

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: dist/
Note: GitHub Actions has its own reuse mechanisms (reusable workflows, composite actions) that work across files. Anchors are best for reducing repetition within a single workflow file.

9. GitLab CI: Template Jobs with Anchors

GitLab CI has first-class support for hidden jobs (prefixed with a dot) that serve as templates. You can use YAML anchors or GitLab's extends keyword to achieve similar results.

Using anchors vs extends:

Using YAML anchors:

# .gitlab-ci.yml — Using YAML anchors
stages:
  - test
  - build
  - deploy

# Hidden job as anchor template (dot prefix = hidden)
.default_job: &default_job
  image: node:20-alpine
  before_script:
    - npm ci
  cache:
    key: $CI_COMMIT_REF_SLUG
    paths:
      - node_modules/
  tags:
    - docker

.deploy_template: &deploy_template
  image: alpine:latest
  before_script:
    - apk add --no-cache curl
  when: manual
  tags:
    - docker

# Jobs using anchors
test:
  <<: *default_job
  stage: test
  script:
    - npm run lint
    - npm test -- --coverage
  coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'

build:
  <<: *default_job
  stage: build
  script:
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 week

deploy_staging:
  <<: *deploy_template
  stage: deploy
  script:
    - curl -X POST "https://api.example.com/deploy?env=staging"
  environment:
    name: staging
    url: https://staging.example.com

deploy_production:
  <<: *deploy_template
  stage: deploy
  script:
    - curl -X POST "https://api.example.com/deploy?env=production"
  environment:
    name: production
    url: https://example.com
  only:
    - main

Using GitLab extends (preferred):

# .gitlab-ci.yml — Using GitLab extends (preferred)
stages:
  - test
  - build
  - deploy

# Hidden template jobs (no anchors needed)
.default_job:
  image: node:20-alpine
  before_script:
    - npm ci
  cache:
    key: $CI_COMMIT_REF_SLUG
    paths:
      - node_modules/
  tags:
    - docker

.deploy_template:
  image: alpine:latest
  before_script:
    - apk add --no-cache curl
  when: manual
  tags:
    - docker

# Jobs using extends (deep merge!)
test:
  extends: .default_job
  stage: test
  script:
    - npm run lint
    - npm test

build:
  extends: .default_job
  stage: build
  script:
    - npm run build

deploy_staging:
  extends: .deploy_template
  stage: deploy
  script:
    - curl -X POST "https://api.example.com/deploy?env=staging"
  environment:
    name: staging
GitLab recommends extends over anchors because extends performs a deep merge and is more readable. However, anchors are still useful for sharing individual values or when you need fine-grained control.

Comparison:

FeatureYAML AnchorsGitLab extends
Merge typeShallowDeep
ReadabilityModerateHigh
Cross-fileNoYes (include)
Standard YAMLYesNo (GitLab-specific)

10. Kubernetes: Common Labels & Resource Limits

Kubernetes YAML manifests often have repetitive metadata, labels, resource limits, and environment variables. While Kubernetes does not process anchors natively (kubectl applies the resolved YAML), you can use anchors in your source files and let the YAML parser resolve them before applying.

Common patterns for Kubernetes:

# kubernetes-manifests.yaml
# Common definitions (using YAML multi-document with ---)

# Shared labels and metadata
_anchors:
  labels: &common-labels
    app.kubernetes.io/part-of: myapp
    app.kubernetes.io/managed-by: kubectl
    team: backend
    environment: production

  resources: &default-resources
    requests:
      cpu: 100m
      memory: 128Mi
    limits:
      cpu: 500m
      memory: 512Mi

  env: &common-env
    - name: LOG_LEVEL
      value: "info"
    - name: TZ
      value: "UTC"
    - name: OTEL_EXPORTER_ENDPOINT
      value: "http://otel-collector:4317"

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
  labels:
    <<: *common-labels
    app.kubernetes.io/name: api-server
    app.kubernetes.io/component: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app.kubernetes.io/name: api-server
  template:
    metadata:
      labels:
        <<: *common-labels
        app.kubernetes.io/name: api-server
    spec:
      containers:
        - name: api
          image: myapp-api:v2.5.0
          resources: *default-resources
          env:
            *common-env
          ports:
            - containerPort: 3000

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: worker
  labels:
    <<: *common-labels
    app.kubernetes.io/name: worker
    app.kubernetes.io/component: worker
spec:
  replicas: 5
  selector:
    matchLabels:
      app.kubernetes.io/name: worker
  template:
    metadata:
      labels:
        <<: *common-labels
        app.kubernetes.io/name: worker
    spec:
      containers:
        - name: worker
          image: myapp-worker:v2.5.0
          resources:
            requests:
              cpu: 200m
              memory: 256Mi
            limits:
              cpu: "1"
              memory: 1Gi       # Worker needs more memory
          env:
            *common-env
Important: Tools like kustomize and Helm are the recommended way to share configuration across Kubernetes manifests. YAML anchors only work within a single document/file. For cross-file reuse, use kustomize overlays or Helm templates.

11. Limitations & Gotchas

While anchors and aliases are powerful, they have several important limitations you should be aware of:

No cross-file anchors

Anchors are scoped to a single YAML document (within a single file, or between --- document separators). You cannot reference an anchor defined in a different file.

# file-a.yaml
database: &db_config
  host: localhost
  port: 5432

# file-b.yaml
service:
  db: *db_config    # ERROR: *db_config is not defined in this file!

# Solution: Keep all anchors and aliases in the same file
# Or use tool-specific features (GitLab include, Helm, etc.)

No JSON compatibility

JSON does not support anchors or aliases. If your YAML is converted to JSON (e.g., for an API), anchors are resolved to their values during parsing. The $ref mechanism in JSON Schema is a different feature entirely.

# YAML with anchors:
defaults: &defaults
  timeout: 30
  retries: 3

service:
  <<: *defaults
  name: api

# Converts to JSON as (anchors resolved):
# {
#   "defaults": { "timeout": 30, "retries": 3 },
#   "service": { "timeout": 30, "retries": 3, "name": "api" }
# }
# No anchor/alias information is preserved in JSON

YAML bombs (billion laughs)

Recursive or deeply nested anchors can create exponentially large data structures, similar to XML billion laughs attacks. This is a known security concern:

# YAML bomb / Billion laughs attack
# WARNING: Do NOT parse this with unlimited settings!
a: &a ["lol","lol","lol","lol","lol","lol","lol","lol","lol"]
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a]
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b]
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c]
e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d]
# Each level multiplies by 9: 9^5 = 59,049 "lol" strings
# More levels = exponential growth = memory exhaustion
Most modern YAML parsers have safeguards against this, but you should always set parsing limits when processing untrusted YAML input.
# Safe parsing examples:

# Python (PyYAML) — use SafeLoader
import yaml
with open('config.yaml') as f:
    data = yaml.safe_load(f)  # SafeLoader prevents code execution

# JavaScript (js-yaml) — default is safe
const yaml = require('js-yaml');
const data = yaml.load(fs.readFileSync('config.yaml', 'utf8'));
// js-yaml has built-in maxAliasCount (default: 100)

# Go (go-yaml) — set limits
decoder := yaml.NewDecoder(reader)
decoder.KnownFields(true)  // Reject unknown fields

Cannot partially modify aliases

An alias (*name) produces an exact copy. You cannot modify individual fields of an aliased mapping without using the merge key (<<). And even with <<, you can only override top-level keys, not nested keys.

# Cannot partially modify an alias
defaults: &defaults
  database:
    host: localhost
    port: 5432
    pool_size: 10

# This REPLACES the entire database mapping, not just pool_size:
staging:
  <<: *defaults
  database:
    pool_size: 5
    # host and port are LOST! << only merges top-level keys.

# Result (NOT what you might expect):
# staging:
#   database:
#     pool_size: 5          # host and port are gone!

# Solution: Anchor at a finer granularity
db_host: &db_host "localhost"
db_port: &db_port 5432

staging:
  database:
    host: *db_host
    port: *db_port
    pool_size: 5            # Only pool_size is different

Parser support varies

The merge key (<<) is defined in the YAML 1.1 specification but was removed from YAML 1.2. However, most parsers still support it for backward compatibility. Check your parser documentation.

Circular references

YAML allows circular references in theory, but most parsers reject them or set recursion limits. Avoid creating anchors that reference themselves.

12. Alternatives to Anchors & Aliases

When YAML anchors are not sufficient for your needs, consider these alternatives:

YAML includes (non-standard)

Some tools support custom !include tags to reference external files. This is not part of the YAML specification but is implemented by tools like Home Assistant, Ansible, and custom YAML loaders.

# Non-standard !include (supported by some tools)
# config.yaml
database: !include database.yaml
logging: !include logging.yaml

# database.yaml
host: localhost
port: 5432
name: myapp

JSON $ref

JSON Schema and OpenAPI use $ref for cross-document references. This is a separate mechanism from YAML anchors and works across files.

# OpenAPI / JSON Schema $ref
paths:
  /users:
    get:
      responses:
        200:
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserList'
        400:
          $ref: '#/components/responses/BadRequest'

Helm templates

For Kubernetes, Helm provides Go templating with values.yaml, named templates, helpers, and conditional logic. Much more powerful than anchors for complex deployments.

# Helm template example (templates/deployment.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "myapp.fullname" . }}
  labels:
    {{- include "myapp.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          resources:
            {{- toYaml .Values.resources | nindent 12 }}

Jsonnet

A data templating language that compiles to JSON. Supports variables, functions, conditionals, imports, and more. Used by Grafana, Tanka, and other tools.

// Jsonnet example
local defaults = {
  replicas: 3,
  image: 'myapp:latest',
  resources: {
    limits: { cpu: '500m', memory: '512Mi' },
  },
};

{
  api: defaults + {
    name: 'api-server',
    ports: [{ containerPort: 3000 }],
  },
  worker: defaults + {
    name: 'worker',
    replicas: 5,  // Override
  },
}

Kustomize

A Kubernetes-native configuration management tool that supports overlays, patches, and cross-file transformations without templates.

Dhall

A programmable configuration language with a type system, imports, and functions. Compiles to YAML, JSON, or other formats.

When to use what:

# When to use what:
# Same-file repetition           -> YAML anchors & aliases
# Cross-file (GitLab CI)         -> extends + include
# Kubernetes config management    -> Kustomize or Helm
# Complex logic / conditionals    -> Jsonnet or Dhall
# API specifications              -> JSON $ref (OpenAPI)
  • Same-file repetition -> YAML anchors & aliases
  • Cross-file sharing in GitLab CI -> extends + include
  • Kubernetes config management -> Kustomize or Helm
  • Complex logic / conditionals -> Jsonnet or Dhall
  • API specifications -> JSON $ref (OpenAPI)
Try our JSON to YAML Converter|Validate your YAML online

13. FAQ

What is the difference between a YAML anchor and an alias?

An anchor (&name) marks a node so it can be referenced later. An alias (*name) is the reference that points back to the anchored node. Think of anchors as "define" and aliases as "use." You must define an anchor before you can use its alias.

Can YAML anchors work across multiple files?

No. YAML anchors are scoped to a single document within a single file. They cannot reference nodes in other files. For cross-file reuse, use tool-specific features like GitLab CI extends with include, Kubernetes Kustomize, Helm templates, or custom YAML loaders with !include tags.

What does <<: *alias do in YAML?

The << is a merge key that inserts all key-value pairs from the aliased mapping into the current mapping. It is similar to object spreading in JavaScript. Local keys take precedence over merged keys, so you can override specific fields while inheriting the rest.

Are YAML anchors a security risk?

They can be. YAML bombs (also called billion laughs attacks) use nested anchors to create exponentially large data structures that exhaust memory. Always set parsing limits when processing untrusted YAML. Most modern parsers (PyYAML SafeLoader, js-yaml safeLoad) have built-in protections against this.

Should I use YAML anchors or GitLab CI extends?

GitLab recommends extends over anchors. extends performs a deep merge (anchors do a shallow merge), works across files when combined with include, and is more readable. Use anchors only when you need to reuse individual scalar values or when extends does not cover your use case.

𝕏 Twitterin LinkedIn
บทความนี้มีประโยชน์ไหม?

อัปเดตข่าวสาร

รับเคล็ดลับการพัฒนาและเครื่องมือใหม่ทุกสัปดาห์

ไม่มีสแปม ยกเลิกได้ตลอดเวลา

ลองเครื่องมือที่เกี่ยวข้อง

Y{}JSON ↔ YAML ConverterYMLYAML Validator & FormatterTYTOML ↔ YAML

บทความที่เกี่ยวข้อง

YAML Multiline String: Block Scalar, Folded และ Literal อธิบาย

เข้าใจตัวเลือก multiline string ของ YAML

ไวยากรณ์ YAML & การตรวจสอบ: ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข

เชี่ยวชาญไวยากรณ์ YAML: กฎการย่อหน้า ข้อผิดพลาดการ parse ชนิดข้อมูล และแนวปฏิบัติที่ดี