
September 11, 2026
14 min read
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.
&name, reference it with *name, and optionally merge it with <<: *name. Aliases point to one shared object—editing the anchor changes every alias.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.
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
- Identify duplicated mapping blocks longer than five lines.
- Extract the shared block into a hidden template key or a top-level anchor.
- Replace copies with
*aliasor merge-key syntax. - Run the consumer's validator:
gitlab-ci-lint,kubectl apply --dry-run=client, ordocker compose config. - Diff the rendered output against the pre-refactor file to confirm parity.
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.
| Approach | Best for | Limitation |
|---|---|---|
| YAML anchors and merge keys | Single-file CI, Compose, local K8s overlays | No cross-file imports; shared reference semantics |
extends in GitLab CI | Job inheritance with UI-visible names | GitLab-specific; not portable to GitHub Actions |
| Kustomize bases and patches | Kubernetes multi-env deploys | Extra tooling; steeper learning curve |
| Helm charts | Parameterized K8s with values files | Template syntax; harder to diff rendered output |
| Jsonnet or yq generation | Large mono-repo config with logic | Build step required; not hand-edited YAML |
| Ansible roles and vars | Server provisioning playbooks | Runtime 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.
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.
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.ymlfor a quick parse check on Linux servers. - Use
yamllintin 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.ymlbefore 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
extendswhen 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
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.

