Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

YAML Explained: Anchors, Aliases, Gotchas

By Kokil Thapa | Last reviewed: September 2026

YAML Explained: Anchors, Aliases, Gotchas is the reference I wish I had before my first GitLab CI refactor blew up a production deploy. YAML looks harmless until you duplicate a forty-line job block three times, change one field in two copies, and wonder why staging passes while production runs the old image tag. Anchors and aliases exist to keep config DRY. They also introduce reference semantics that bite when you expect copy-paste behaviour. This guide walks through real syntax, merge keys, parser differences, and the failure modes I see on Linux deployment pipelines and Laravel projects every month.

What are YAML anchors and aliases in plain terms?

YAML is a human-readable data serialisation format. Parsers turn it into native structures in PHP, Python, Ruby, Go, and JavaScript. Most teams touch YAML through Docker Compose, Kubernetes manifests, Ansible playbooks, GitHub Actions, Azure DevOps, or GitLab CI for PHP projects. JSON covers APIs. YAML covers config files humans edit in Git.

An anchor labels a node once. An alias reuses that node elsewhere in the same document. Think of the anchor as a named bookmark and the alias as a pointer back to it. The YAML 1.2 specification calls this an anchor node and an alias node. Parsers resolve aliases after the full document is read.

This differs from a template engine or variable substitution. There is no runtime variable store. The alias is a structural reference inside one parse tree. Change the anchored block and every alias sees the change on the next parse. That is powerful for maintenance. It is dangerous when you assumed each alias owned its own copy.

YAML Anchors and AliasesAnchor Node&database_defaultsAlias Use*database_defaultssame objectParsed Document Treehost: dbport: 5432staging jobpoints hereprod jobpoints here
YAML Explained: anchors define one node; aliases reference that single parsed object

On a production Laravel application I maintain, the same Redis connection block appears in queue worker config, Horizon config, and a health-check script wrapper. Anchors keep those three blocks identical without a build step. If your stack lives in custom application config files, anchors are often enough. If you need cross-file reuse, look at templating tools instead.

How do you write YAML anchors and aliases step by step?

Start with the smallest repeated block. Add an ampersand and a name immediately after the key or scalar you want to anchor. Reference it with an asterisk and the same name. Names are local to the document. They cannot contain spaces. Stick to letters, digits, and underscores.

Scalar and sequence anchors

Scalars and lists anchor cleanly. This pattern works in Docker Compose and local dev stacks:

common_env: &common_env
  APP_ENV: local
  LOG_LEVEL: debug

web:
  environment:
    <<: *common_env
    APP_NAME: storefront

worker:
  environment:
    <<: *common_env
    APP_NAME: queue-worker

The <<: token is a merge key. It splices the anchored mapping into the current mapping. Keys on the right override keys from the anchor. Merge keys are a YAML 1.1 feature. Most parsers used in DevOps still support them. Some strict YAML 1.2-only tools reject merge keys. Know your consumer before you rely on them.

Mapping anchors for CI job templates

GitLab CI and Azure DevOps both benefit from a hidden template job. The job never runs. Child jobs inherit script, image, and cache settings through aliases:

.php_test_template: &php_test_template
  image: php:8.3-cli
  before_script:
    - composer install --no-interaction
  script:
    - vendor/bin/phpunit

unit_tests:
  <<: *php_test_template
  stage: test

integration_tests:
  <<: *php_test_template
  stage: test
  services:
    - mysql:9.7

That structure mirrors what I document in our Azure DevOps YAML pipeline guide. PHP 8.3 satisfies Laravel 13's minimum. Laravel 12 projects can run on PHP 8.2. Match the image tag to your composer.json constraint before you copy this block.

Ordered steps to refactor safely

  1. Identify duplicated mapping blocks longer than five lines.
  2. Extract the shared block into a hidden template key or a top-level anchor.
  3. Replace copies with *alias or merge-key syntax.
  4. Run the consumer's validator: gitlab-ci-lint, kubectl apply --dry-run=client, or docker compose config.
  5. Diff the rendered output against the pre-refactor file to confirm parity.
Safe YAML Anchor Refactor1. Find dupes3+ identical blocks2. Add anchor&template_name3. Alias jobs*template_name4. Validatelint + dry-runBefore vs AfterBefore: 120 linessame script x 4 jobsdrift on one copyAfter: 45 linesone anchor blocksingle edit point
Refactor duplicated YAML into anchors, then validate with your pipeline or orchestrator linter

When should you use anchors versus other DRY approaches?

Anchors shine inside one file with moderate duplication. They fail when you need reuse across repositories, conditional logic, or secret injection. Pick the right tool before you force YAML to behave like a programming language.

ApproachBest forLimitation
YAML anchors and merge keysSingle-file CI, Compose, local K8s overlaysNo cross-file imports; shared reference semantics
extends in GitLab CIJob inheritance with UI-visible namesGitLab-specific; not portable to GitHub Actions
Kustomize bases and patchesKubernetes multi-env deploysExtra tooling; steeper learning curve
Helm chartsParameterized K8s with values filesTemplate syntax; harder to diff rendered output
Jsonnet or yq generationLarge mono-repo config with logicBuild step required; not hand-edited YAML
Ansible roles and varsServer provisioning playbooksRuntime execution, not static manifest

For Kubernetes teams, I often pair anchors inside a base manifest with Kustomize for environment overlays. That split keeps day-to-day edits readable. See our notes on writing Terraform and Kubernetes YAML for larger topology files. Terraform itself uses HCL, not YAML anchors. Provider alias blocks there solve a different problem—covered in the Terraform provider aliases article.

Shell aliases on Ubuntu servers are unrelated syntax. They save keystrokes in bash. YAML anchors save bytes in config. The naming collision confuses junior devs. Our Ubuntu command aliases guide covers bash only.

What are the worst YAML anchor and alias gotchas?

Most production YAML failures I debug are indentation errors or tab characters. Anchor bugs rank second. They fail quietly because the file parses. The semantics surprise you at runtime.

Gotcha 1: Aliases share one object, not a copy

If you anchor a list and alias it twice, appending to one alias appends to both. Some parsers treat merged mappings as copies. Others preserve identity for nested nodes. Never assume copy semantics without checking your tool's behaviour. The official YAML 1.2.2 specification defines alias nodes as references to anchored nodes.

tags: &tag_list
  - php
  - laravel

job_a:
  tags: *tag_list

job_b:
  tags: *tag_list

Both jobs share the same sequence node after parsing. If your CI runner mutates tags in memory for one job, the other job may see the mutation. Static config files rarely mutate. Dynamic generators sometimes do. Treat aliases as shared state.

Gotcha 2: Merge key order and overrides

Keys that appear after <<: *anchor override keys from the anchor. Keys before the merge key do not override the anchor unless your parser documents otherwise. When in doubt, put the merge key first inside the mapping:

deploy_prod:
  <<: *deploy_template
  environment: production
  when: manual

Reverse the order in some parsers and you get stale values with no syntax error. That is a silent gotcha worth a dry-run diff every time.

Gotcha 3: Anchors cannot cross documents

Multi-document YAML files separate chunks with ---. Anchors defined in document one are invisible in document two. Kubernetes applies one resource per document in many workflows. Keep anchors within the same document boundary.

Gotcha 4: Tabs, quotes, and boolean coercion

YAML forbids tab indentation. Most editors insert spaces. A single tab on line twelve breaks the parser before it reaches your anchor. Unquoted yes, no, on, and off become booleans in YAML 1.1 mode. PHP 8.5 and Node.js 26 LTS projects often consume YAML through libraries still defaulting to 1.1 rules. Quote country codes and version strings when they look like booleans.

Gotcha 5: Double alias and circular references

An alias cannot point to another alias in some strict modes. Circular anchors—A anchors B and B anchors A—make parsers recurse until they error. If you need indirection, flatten the structure or move logic into code.

YAML Gotcha Decision TreeConfig behaves oddly?Parse error?tabs / bad indentParses OK?check alias semanticsShared referenceedit anchor onceMerge orderoverride after <<:Cross docanchor not visibleFix: render config and diff against known-good outputUse /tools/json-formatter for JSON side-by-side checks
YAML anchor gotchas: parse errors versus silent semantic bugs need different fixes

Compare rendered YAML to JSON when your toolchain exposes a JSON view. Our JSON formatter tool helps eyeball structural diffs after a refactor. For Markdown docs stored beside config, the Markdown to HTML converter stays unrelated to parsing—but teams often maintain both in the same repo.

How do anchors work in GitLab CI, Docker Compose, and Kubernetes?

Each consumer parses YAML with slightly different extensions. GitLab CI adds extends and include. Docker Compose v3 supports merge keys in practice. Kubernetes accepts multi-document files but ignores unknown fields only after strict schema validation per resource kind.

GitLab CI hidden jobs

Prefix template job names with a dot so GitLab skips them in the pipeline graph. Combine dot jobs with anchors when you want both GitLab inheritance and a parse-time alias inside an included file:

.deploy_rules: &deploy_rules
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual
    - when: never

deploy_staging:
  <<: *deploy_rules
  environment: staging

Validate through the GitLab CI lint API or the pipeline editor before merge. I have seen anchor names collide when two included files define &default. Prefix anchor names with the file purpose: &php_unit_template, not &default.

Docker Compose service templates

Compose files for local Laravel stacks often repeat volume mounts and network settings. Anchor the shared fragment once per file:

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

services:
  app:
    logging: *default_logging
  queue:
    logging: *default_logging

The x- extension field prefix marks custom keys some Compose versions ignore at the top level. Patterns vary by Compose specification version. Run docker compose config to expand aliases and catch errors before deploy.

Kubernetes manifests and Kustomize

Raw Kubernetes YAML rarely uses anchors in upstream examples. Teams add them in private overlays to DRY container specs. The API server receives JSON after kubectl conversion. Anchors must survive that conversion in your client version. Prefer Kustomize patchesStrategicMerge when multiple engineers edit the same base. Anchors suit small internal clusters where one senior dev owns the manifest.

The Kubernetes documentation on configuration best practices recommends keeping manifests explicit over clever. That advice applies double to anchors. Clever YAML saves lines. Explicit YAML saves on-call hours. On sister sites sharing our Deployer 7 pipeline—legal portals like Notary Nepal and translation services—we keep production K8s bases anchor-free and push DRY logic into Helm or Kustomize when complexity grows.

Anchors Across DevOps ToolsGitLab CIhidden dot jobs& + extendsPHP 8.3 test imageDocker Composex- extension keysshared logging envcompose configKubernetesper-document anchorsprefer Kustomizekubectl dry-runLaravel Deploy Pathgit pushCI lintdep deployliveBad anchor in .gitlab-ci.yml fails before Deployer 7 reaches the server
YAML anchors in CI and Compose feed the same Git-based deploy flow used on production Laravel sites

Ansible playbooks use YAML syntax but solve reuse through roles and vars files. Encrypted secrets belong in Ansible Vault, not anchored plaintext blocks. Read the Ansible Vault encryption guide before you anchor database passwords in a playbook. For API gateway config stored as YAML, our API development service treats schema validation as part of the delivery pipeline.

How do you debug YAML parsing and alias errors quickly?

Start with the parser error line number. YAML parsers report the first failure. The root cause often sits ten lines above. Fix indentation before you chase anchor names.

  • Run python3 -c "import yaml,sys; yaml.safe_load(open(sys.argv[1]))" file.yml for a quick parse check on Linux servers.
  • Use yamllint in CI alongside PHPCS or ESLint; add it to the same stage as unit tests.
  • Render Compose and GitLab configs to canonical form and store the diff in the merge request.
  • Search for duplicate anchor names with grep -n '&' .gitlab-ci.yml before merge.
  • Replace merge keys temporarily with explicit copies to see if behaviour changes—that isolates alias bugs fast.

On booking platforms like Adventure Third Pole Trek, a broken CI YAML file blocked deploys during peak season. The anchor name had a typo. The alias resolved to null in one job only. Lint caught it after we added strict mode. Budget thirty minutes of pipeline hardening. It pays back the first time anchors save you from copy-paste drift.

PHP projects sometimes embed YAML in config caches. Symfony 8.1 apps load YAML natively. Laravel favours PHP config arrays but uses YAML in CI and Docker. Type coercion gotchas in PHP differ from YAML boolean rules—see the PHP type coercion guide for runtime issues after YAML becomes PHP arrays.

Enterprise clients evaluating enterprise application development often ask for config standards. My default policy: anchors allowed in CI and local dev files; production Kubernetes bases stay explicit unless the team documents parser versions. Testing and optimization passes should include a YAML lint stage. That is cheap insurance.

Key Takeaways

  • Define anchors with &name, reference with *name, and merge mappings with <<: *name—aliases point to one shared node, not a deep copy.
  • Put merge keys first inside a mapping so local keys reliably override anchored defaults.
  • Keep anchors inside a single YAML document; prefix names to avoid collisions across included files.
  • Validate with tool-specific commands—gitlab-ci-lint, docker compose config, kubectl apply --dry-run=client—before merge.
  • Prefer Kustomize, Helm, or GitLab extends when reuse crosses files or needs conditionals.
  • Never anchor secrets; use vault tools and environment injection instead of DRY plaintext blocks.

People Also Ask

Can YAML anchors reference nodes in another file?

No. Anchors and aliases work within one YAML document or one multi-document stream per parse. Cross-file reuse requires include directives in GitLab CI, Kustomize resources, or a templating layer that emits YAML before parsing.

Are merge keys part of YAML 1.2?

Merge keys were defined in YAML 1.1 and widely implemented by PyYAML, Ruby's psych, and DevOps tooling. Strict YAML 1.2 parsers may reject <<:. Confirm your consumer's parser mode before relying on merge semantics in production.

Do GitHub Actions support YAML anchors?

GitHub Actions workflows are YAML files, and the parser accepts anchors and aliases in practice. Many teams still use reusable workflows and composite actions instead because those features are easier to review in the GitHub UI than hidden anchor indirection.

What is the difference between a YAML alias and a variable?

A YAML alias is resolved at parse time into a reference to an existing node in the document tree. A CI variable like $CI_COMMIT_SHA is substituted at runtime by the platform. They operate in different phases and cannot replace each other.

Put YAML anchors to work without the surprise failures

You now have YAML Explained: Anchors, Aliases, Gotchas in one place—syntax, merge keys, tool-specific behaviour, and the silent failures that waste deploy windows. Start with one duplicated CI job or Compose service block this week. Extract an anchor, run your linter, and diff the rendered output. That small refactor is the lowest-risk way to prove aliases behave the way you expect before you trust them in production.

If your team maintains large GitLab CI files, Kubernetes overlays, or Ansible playbooks alongside a Laravel or Symfony codebase and wants a second pair of eyes on config architecture, contact us for a review. You can also browse the portfolio for examples of Git-based deploy pipelines we run in production, or read more on the blog about CI and infrastructure topics. For hands-on help shipping reliable config with your next release, explore support and maintenance options.

Frequently Asked Questions

An anchor labels a node once with &name. An alias reuses that same node elsewhere with *name. Parsers resolve aliases after reading the full document—they are structural references inside one parse tree, not copy-paste or runtime variables.

Start with the smallest repeated block. Add an ampersand and name immediately after the key or scalar you want to anchor, using letters, digits, and underscores only. Reference it elsewhere with an asterisk and the same name. For mappings, use

The

Aliases point to one shared object, not a deep copy. If you anchor a list and alias it twice, both aliases reference the same sequence node after parsing. Editing or mutating through one alias can affect every other alias. Some parsers treat merged mappings differently for nested nodes, so never assume copy semantics without checking your consumer tool behaviour.

Keys that appear after

No. Multi-document YAML files separate chunks with ---. Anchors defined in document one are invisible in document two. Keep anchors within the same document boundary, which matters for Kubernetes workflows that apply one resource per document.

Anchors shine inside one file with moderate duplication—CI pipelines, Docker Compose, local Kubernetes overlays. They fail when you need reuse across repositories, conditional logic, or secret injection. For Kubernetes multi-environment deploys, Kustomize bases and patches or Helm values files scale better. GitLab extends adds job inheritance with UI-visible names but is GitLab-specific and not portable to GitHub Actions. Pick the tool before forcing YAML to behave like a programming language.

Prefix template job names with a dot so GitLab skips them in the pipeline graph. Anchor the shared block on that hidden job, then merge it into child jobs with

Anchor shared fragments such as volume mounts, network settings, or logging blocks once per Compose file. Reference them in multiple services with *alias or merge them with

Raw Kubernetes YAML rarely uses anchors in upstream examples. Teams sometimes add them in private overlays to DRY container specs, but the API server receives JSON after kubectl conversion and anchors must survive that conversion in your client version. Kubernetes documentation recommends keeping manifests explicit over clever. Prefer Kustomize strategicMerge patches when multiple engineers edit the same base. Clever YAML saves lines; explicit YAML saves on-call hours.

YAML forbids tab indentation—a single tab can break the parser before it reaches your anchor. Unquoted yes, no, on, and off become booleans in YAML 1.1 mode, which many DevOps parsers still default to; quote country codes and version strings that look like booleans. Circular anchors where A references B and B references A make parsers recurse until they error. An alias pointing to another alias may fail in strict modes. Flatten the structure or move logic into code when indirection grows.

Start with the parser error line number, then check indentation ten lines above—the root cause often sits above the reported failure. Run python3 -c "import yaml,sys; yaml.safe_load(open(sys.argv[1]))" file.yml for a quick parse check on Linux. Add yamllint to CI alongside PHPCS or ESLint. Render Compose and GitLab configs to canonical form and diff the output. Search for duplicate anchor names with grep -n '&' .gitlab-ci.yml. Temporarily replace merge keys with explicit copies to isolate alias bugs. Validate with gitlab-ci-lint, docker compose config, or kubectl apply --dry-run=client.

No. Never anchor secrets in plaintext blocks just to stay DRY. Encrypted secrets belong in Ansible Vault or equivalent vault tools, with runtime injection through environment variables—not reusable anchored nodes in a Git-tracked file. Anchors are fine for CI and local dev config where values are non-sensitive. Production Kubernetes bases should stay explicit unless the team documents parser versions and secret handling policy.

Both jobs share the same sequence node after parsing. If your CI runner mutates tags or list items in memory for one job, the other job may see that mutation. Static config files rarely mutate at runtime, but dynamic generators sometimes do. Treat aliases as shared state. Compare rendered YAML to JSON when your toolchain exposes a JSON view to eyeball structural diffs after a refactor and confirm both jobs received the values you expected.

Identify duplicated mapping blocks longer than five lines. Extract the shared block into a hidden template key or top-level anchor. Replace copies with *alias or merge-key syntax. Run the consumer validator—gitlab-ci-lint, kubectl apply --dry-run=client, or docker compose config—before merge. Diff the rendered output against the pre-refactor file to confirm parity. Budget time for pipeline hardening; a thirty-minute lint setup pays back the first time anchors prevent copy-paste drift between staging and production image tags.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: