I remember the first time I ran into YAML anchors. I was trying to set up a ridiculously complex Docker Compose file, and suddenly, boom. Error. Something about a duplicate key, but I swear I wasn’t copying and pasting like a madman. I spent a good hour digging through Stack Overflow, feeling like I was drowning in obscure jargon.
It turns out, a lot of the confusion around YAML anchors, especially whether they are scoped per document, stems from how they’re handled and how people expect them to work versus how they actually work. It’s not as simple as a global find-and-replace, and that’s where the real headaches start.
So, let’s cut through the noise. Are YAML anchors scoped per document? The short answer is yes, but that ‘yes’ comes with some important caveats that can trip you up if you’re not careful. This isn’t some corporate whitepaper; it’s me, telling you what I learned the hard way.
Why You’re Probably Wrong About Yaml Anchor Scope
Look, everyone online seems to say YAML anchors are scoped to the document. And for the most part, they’re right.
If you define an anchor `&my_common_settings` in a single YAML file, and then use `*my_common_settings` later in that same file, it’s going to work like a charm. It’s like having a little alias for a chunk of YAML that you can reuse. This is the bread and butter of what anchors are designed for – reducing repetition and making your configuration files cleaner.
Think about defining a standard set of environment variables for multiple services in Docker Compose, or setting up default logging configurations. Instead of typing out the same block of key-value pairs over and over, you define it once with an anchor and then reference it wherever you need it.
It’s supposed to make your life easier, and when it works, it really does. I’ve used it to define common network settings for a dozen microservices in a Kubernetes manifest, and it saved me a ton of typing and potential typos.
The real kicker, and where I see most people go wrong – myself included, early on – is when they start thinking about multiple YAML files. If you have `file1.yaml` and `file2.yaml`, and both have an anchor named `&default_ports`, you might assume these are entirely separate.
And they are! An anchor defined in `file1.yaml` has absolutely no visibility or effect in `file2.yaml`. This is the core of the per-document scoping.
The YAML parser processes each file independently when resolving anchors. So, if you’re loading multiple YAML files using a tool or a library that just concatenates them or processes them sequentially without explicitly merging context, you’re going to hit issues if you try to reuse anchor names across files.
This is particularly relevant in CI/CD pipelines where you might be assembling configuration from different sources. The parser doesn’t magically know that `&default_ports` in your `base.yaml` should be the same as `&default_ports` in your `environment.yaml`.
They are distinct entities.
My first major screw-up involved a CI/CD script that was supposed to load a `defaults.yaml` file, then an `environment-specific.yaml` file, and then combine them. I had a common `&db_config` anchor in `defaults.yaml`.
When `environment-specific.yaml` tried to reference `*db_config`, it failed because, to the parser at that stage, `*db_config` didn’t exist. It was looking for it within the scope of `environment-specific.yaml` itself, not in the previously loaded `defaults.yaml`.
The fix wasn’t complex, but the realization was – you can’t assume cross-file visibility for anchors unless your tooling specifically handles that merging and context management. This is the important point: the anchors themselves are defined and resolved within the boundaries of a single YAML document stream. Anything outside that stream is a new scope.
When Anchors Go Rogue: The Phantom Duplicates
This is where things get spicy. While the general rule is ‘per document,’ the devil is in the details of how that document is presented to the YAML parser. Most of the time, when you’re working with a single `.yaml` file, the scope is exactly that file. Simple. But what happens when you’re dealing with data that’s represented as a single document to the parser, even if it originates from multiple sources? This is where the ‘per document’ rule can feel like it’s bending, or even breaking, and you end up with what feels like ‘phantom’ duplicate keys or undefined anchors. (See Also: Can Concrete Anchors Be Used In Brick )
The most common culprit here is when multiple YAML documents are embedded within a single stream, often separated by `—`. A single file might contain several independent YAML documents.
For example, you might have a file with a Kubernetes `Service` definition, followed by a `Deployment` definition, each separated by `—`. In this scenario, anchors defined within the first document are NOT available in the second document.
Each document, separated by `—`, is treated as a distinct scope. So, if you defined an anchor in the `Service` document and tried to use it in the `Deployment` document, it would fail.
This isn’t a violation of the ‘per document’ rule; it’s an illustration of how YAML defines document boundaries. The `—` is the delimiter for separate documents, and each gets its own scope for anchors.
I learned this the hard way when generating complex Terraform configurations that outputted multiple Kubernetes manifests. I had a helper function that generated a common `&network_defaults` anchor in one manifest block. When it was later used in a subsequent manifest block within the same generated file, separated by `—`, the anchor was undefined.
I was pulling my hair out, convinced I was seeing some kind of global scope leak. The reality was that the `—` was creating a new document scope for the parser. The fix involved making sure that if I needed to reuse an anchor across these document boundaries, I had to either redefine it in each document (which defeats the purpose) or, more practically, make sure that the tooling I was using to generate the YAML was smart enough to handle the context, or restructure the output so that all reusable anchors were within the first document if subsequent documents needed to reference them (a less ideal solution).
Here’s a practical way to think about it: imagine a stack of papers. Each paper is a document. If you write a note on the first paper, you can’t see it from the second paper unless you explicitly copy it over. The `—` is like inserting a new sheet of paper. The YAML parser is like someone reading those papers one by one, and they can only see what’s on the current paper or what they’ve explicitly copied (which YAML anchors don’t do across document boundaries). So, when you see an ‘undefined anchor’ error, and you know the anchor is defined, check for those `—` separators. They are the silent killers of cross-document anchor references in a single stream.
The Contradiction: Why You might Think They Aren’t
Now, for the contrarian take. Everyone says YAML anchors are scoped per document. I get it. I said it myself. But sometimes, it feels like they’re not. Or rather, the tools that process YAML can make it seem like they are global, and that’s where the confusion really festers. Let me explain.
When you’re using a tool like `kubectl` to apply a YAML file, or a library like PyYAML in Python, they often load the entire content of a file (or a string) and then parse it. If that content contains multiple documents (separated by `—`), the library or tool might present you with a list of documents. However, some libraries or older tools might process this as a single, continuous stream where anchor resolution happens sequentially. If an anchor is defined, and then used later in that same stream, it works.
The confusion arises when you have multiple separate files being loaded and merged by a higher-level tool (like Ansible, Helm, or even a custom script) before they are presented to a YAML parser. The tool might merge them in a way that makes anchors appear to be cross-file, but that’s the tool’s magic, not the YAML spec’s.
Consider this: if you have `file1.yaml` with `&config` and `file2.yaml` that wants to use `*config`. If you simply cat `file1.yaml` and then `file2.yaml` and pipe that into `yq` or `python -c ‘import yaml; print(yaml.safe_load(sys.stdin))’`, you’ll likely get an ‘undefined anchor’ error.
The parser sees `file1`’s content, then `file2`’s content, and the scope of `&config` ended with `file1`. However, if you’re using a tool like Ansible, and you have a `vars_files` directive that loads `file1.yaml` and then `file2.yaml`, Ansible’s internal processing might make variables defined in `file1` available to templates that process `file2`. This is NOT YAML anchors working across files; this is Ansible’s variable merging and templating engine at play.
It’s mimicking the effect of global variables, but the underlying YAML itself, if parsed in isolation, would still treat anchors per document.
I once spent three days debugging a Helm chart. I had default values in `values.yaml` and overrides in a `production.yaml` file. I used anchors in `values.yaml` to define common configurations.
When `production.yaml` was loaded, it seemed like the anchors weren’t there. My mistake was assuming Helm’s value merging would magically preserve YAML anchor context between files. It doesn’t. (See Also: Can Cords Be Used To Make Anchors Climbing )
Helm merges the data structures that result from parsing each YAML file. If `values.yaml` has `anchor_example: &common_ports { http: 8080, https: 8443 }` and `production.yaml` has `service_ports: *anchor_example`, it will fail.
But if `production.yaml` has `service_ports: {{ .Values.anchor_example }}` and Helm merges `anchor_example` from `values.yaml` into the rendered values for `production.yaml`, then it works. The key difference is the timing and mechanism of resolution.
The YAML parser resolves anchors within its input document. Higher-level tools resolve their own constructs (like variables, includes, merges) after or during the parsing of individual documents.
What to Look for: The Subtle Signals
When you’re working with YAML, especially in complex configurations or when combining multiple files, you need to be vigilant about how anchors are behaving. It’s not just about writing them correctly; it’s about understanding the context in which they’re being processed. The first and most obvious signal is an error message. If you’re getting an ‘undefined anchor’ or a ‘duplicate key’ error, that’s your cue to investigate the scoping.
Here’s a breakdown of what to look for:
- The `—` Separator: As mentioned, this is the most explicit boundary. Every section of YAML separated by `—` is a distinct document. Anchors defined before a `—` are not available after it within the same stream. If your tool presents you with a list of documents from a single file, treat each item in that list as its own scope.
- File Boundaries (for most tools): When processing separate files (e.g., `file1.yaml`, `file2.yaml`), anchors defined in `file1.yaml` are not available in `file2.yaml` unless a specific merging or inclusion mechanism from your tool makes them so. The YAML parser itself doesn’t bridge this gap.
- Tool-Specific Merging/Inclusion: This is where it gets fuzzy. Tools like Ansible, Jinja2 templating, Kustomize, or Helm have their own ways of handling includes, variable inheritance, or merging values from multiple files. These mechanisms often operate after individual YAML documents are parsed. So, a variable defined in one file might be made available to another, and if that variable contains the data that was originally associated with an anchor, it can appear as if the anchor is working across files. But it’s the tool’s logic, not the raw YAML anchor mechanism.
- Alias Usage (`*`): When you use an alias (`*anchor_name`), the YAML parser looks for a corresponding anchor (`&anchor_name`) within the current scope. If it doesn’t find it, you get an error. The scope is typically the current document.
- Anchor Definition (`&`): The definition of an anchor is simply a label applied to a node (a key-value pair, a list, etc.). Its existence and discoverability are what matter for aliases.
My own experience with Kustomize was a prime example of this. Kustomize merges different YAML manifests. If I had a common base configuration with an anchor, and then a Kustomize overlay that referenced it, it wouldn’t work directly. Kustomize merges the resources, and the anchor resolution happens per resource (which is basically per document in this context). To make it work, I often had to make sure the common parts were defined in a way that Kustomize’s merging preserved the structure correctly, or more commonly, use Kustomize’s own patching or variable substitution features rather than relying on raw YAML anchors across the Kustomize build process.
Here’s a handy table to summarize when anchors typically work and when they don’t, based on common scenarios:
| Scenario | Anchor Scope | Verdict |
|---|---|---|
| Single YAML file, no `—` separators | The entire file (one document) | Works |
| Single YAML file with `—` separators | Each section between `—` (multiple documents) | Scoped per document; anchors do NOT cross `—` |
| Multiple separate YAML files processed sequentially by a basic YAML parser | Each file (implicitly, one document per file input) | Scoped per file; anchors do NOT cross file boundaries |
| Multiple files merged by a higher-level tool (e.g., Ansible, Helm values) | Depends entirely on the tool’s merging/templating logic. YAML anchors may be resolved before merging, or the tool might resolve its own variables that mimic anchors. | Can appear to work across files, but it’s tool-dependent, not pure YAML anchor scope. Be cautious. |
Common Mistakes and How to Avoid Them
Alright, let’s talk about the pitfalls. Based on my own war stories and watching others stumble, there are a few recurring mistakes when dealing with YAML anchors and their scoping. Avoiding these will save you a boatload of debugging time.
The most frequent offender is the assumption of global scope. People see an anchor defined, and they think, “Great, I can use this anywhere.” Nope. As we’ve hammered home, the scope is the document. So, mistake number one is trying to reference an anchor defined in `file_a.yaml` from within `file_b.yaml` without any explicit merging or inclusion mechanism in place that preserves context. This leads to those infuriating ‘undefined anchor’ errors that make you question reality.
Another common pitfall involves the `—` document separator. People might put multiple independent configurations into a single file, perhaps for convenience, and then expect anchors to flow between them. This is a classic case of misunderstanding how `—` breaks a single YAML stream into multiple documents. If you have a Kubernetes manifest file that defines a `ConfigMap` and then a `Deployment`, and both need a common set of labels defined by an anchor, you can’t define the anchor in the `ConfigMap` section and expect it to be available in the `Deployment` section. Each is its own document scope.
I’ve personally fallen for the ‘tool magic’ trap. You’re using a tool that seems to merge files or variables, and you assume the YAML anchors are just part of that merge. For instance, with templating engines like Jinja2 (used by Ansible), you can define a variable in one file and use it in another. If that variable holds a structure that could have been an anchor, it feels similar.
But the YAML parser is resolving anchors within each document it parses. The templating engine is resolving its own variables.
When you combine them, you get the end result. My mistake was thinking the YAML parser was carrying the anchor context between files when it was actually the templating engine that made the data available.
Here’s how to sidestep these landmines:
- Explicitly define anchors within each document if they are truly independent. If you have separate documents (separated by `—`) or separate files and no sophisticated merging, redefine your anchors. Yes, it’s repetitive, but it guarantees it will work.
- Use your tool’s intended merging or inclusion features. If you’re using Ansible, use `vars_files` or `include_vars` correctly. If it’s Helm, use `values.yaml` and override files as designed. These tools often have mechanisms to make data from one source available to another, which can act like shared configuration, but it’s not raw YAML anchor scope.
- Consolidate reusable anchors into a single file or document. If possible, have one file (or the first document in a multi-document stream) that contains all your common anchors, and then reference them from other documents if your tool supports loading/merging them in that order and context.
- Be aware of the `—` separator. Always assume that `—` creates a new, independent scope for anchors. If you need shared definitions, put them in the first document of the stream, or in a separate file processed before the referencing document.
- When in doubt, simplify. Break down complex, multi-file YAML configurations. Test anchor resolution on a smaller, isolated set of documents first.
My biggest revelation was realizing that sometimes, the ‘DRY’ (Don’t Repeat Yourself) principle in YAML isn’t about sharing YAML anchor definitions across files automatically. It’s about structuring your data so that your tools can merge or include it effectively. The YAML anchor is a feature of the YAML specification for a single document stream, not a cross-file variable system. Treat it as such, and you’ll be much happier. (See Also: Can Anchors In Your Shoulder Break )
Real-World Use Cases: Where Anchors Shine (and Where They Don’t)
So, where do YAML anchors actually prove their worth? When you understand their scope, they are incredibly useful for reducing verbosity and errors within a single configuration file or a set of documents processed together as a single stream. The most common and effective use case is within large, complex Kubernetes manifests or Docker Compose files.
Imagine a Kubernetes `Deployment` manifest that defines multiple containers, each with similar resource limits, environment variables, or liveness/readiness probes. Instead of repeating the same block of `resources: { limits: { cpu: ‘100m’, memory: ‘128Mi’ }, requests: { cpu: ’50m’, memory: ’64Mi’ } }` for every container, you can define it once with an anchor:
common_resources: &common_resources
limits:
cpu: '100m'
memory: '128Mi'
requests:
cpu: '50m'
memory: '64Mi'
containers:
- name: app-container
image: my-app:latest
resources: *common_resources
- name: sidecar-container
image: my-sidecar:v1.0
resources: *common_resources
env:
- name: LOG_LEVEL
value: "info"
This is clean, readable, and if you need to adjust those limits (say, increase memory to 256Mi), you change it in one place, and it’s updated everywhere within that document. This is the sweet spot for YAML anchors.
Another excellent use is defining common metadata. In Kubernetes, you might have a set of labels or annotations that apply to multiple resources. You can define them once:
common_labels: &common_labels app: my-application environment: production managed-by: kubernetes # ... later in the same document ... metadata: *common_labels # ... and again for another resource ... metadata: *common_labels ```
This is fantastic for maintaining consistency across your manifests. If you decide to add a `team: backend` label to everything, you add it once under `common_labels`, and it propagates.
Where they don’t shine, and where you should avoid them, is trying to use them for sharing configuration across independent files or across documents separated by `—`. For instance, if you have a `database.yaml` file with some connection details defined using an anchor, and a `webserver.yaml` file that needs to reference those same details. You cannot simply reference `*db_connection_details` from `database.yaml` within `webserver.yaml` if they are parsed as separate files. You would need a tool to explicitly load and merge them, making those details available as variables or data structures that the second file can then access.
I learned this when setting up a microservices architecture with separate deployment configurations for each service. I wanted to define a standard database connection string structure with an anchor. When I tried to reference it from another service’s configuration file, it failed spectacularly. The YAML parser saw two distinct inputs. The solution wasn’t to contort the YAML anchors, but to use the infrastructure-as-code tool’s (in that case, a custom Python script orchestrating deployments) features to load common variables into a shared context before generating the final YAML for each service.
Basically, think of YAML anchors as a form of local, in-document DRY. They are not a global configuration management system. If you need to share configuration across distinct YAML files, you need a higher-level tool or process to handle that cross-file context. Relying on anchors for that is like trying to use a screwdriver as a hammer – you’ll eventually break something.
Faq: Your Burning Questions Answered
Are Yaml Anchors Scoped Per Document?
Yes, fundamentally, YAML anchors are scoped per document. This means an anchor defined in one YAML document is only available for use within that same document. A document is typically a single YAML file, or a section of a file separated by the `—` document separator.
Can I Use an Anchor Defined in One Yaml File in Another Yaml File?
Generally, no, not directly. The YAML specification treats each file as a separate scope for anchors. If you need to share configurations between files, you must use a higher-level tool or scripting mechanism to load, merge, or pass variables between them. The YAML parser itself does not manage cross-file anchor scope.
What Is the `—` Separator in Yaml?
The `—` sequence is a document separator. It indicates the end of one YAML document and the beginning of another within a single YAML stream (e.g., a single file). Each document delineated by `—` is treated as an independent scope for things like YAML anchors.
How Can I Reuse Configuration Across Multiple Yaml Files?
You need to use a tool or script. This could involve using features like variable inclusion in templating engines (Jinja2, EJS), configuration management tools (Ansible, Puppet), or build tools that merge or import files. These tools create a shared context or merge data structures, allowing you to reference values that might have originated from different sources, effectively mimicking cross-file sharing without relying on raw YAML anchors.
What’s the Difference Between an Anchor (`&`) and an Alias (`*`)?
An anchor (`&`) is used to define a label for a specific node (a scalar, sequence, or mapping) within a YAML document. An alias (`*`) is used to reference a previously defined anchor, inserting the anchored node’s value at that point. The alias uses the label provided by the anchor.
Conclusion
So, to circle back: are YAML anchors scoped per document? Overwhelmingly, yes. If you’re working within a single file that contains one document, or even multiple documents separated by `—` where you’re processing them as distinct entities, the scope is contained. This is the core design. The confusion often arises not from the YAML spec itself, but from the tools and workflows we use that process YAML. These tools can merge files, substitute variables, and perform other operations that make configurations appear to be shared globally, but that’s the tool’s doing, not the YAML anchor’s native behavior.
My advice? Treat anchors as a powerful in-document shortcut. Embrace them for cleaning up repetitive structures within a single, complex manifest. But when you need to share configurations across separate files, reach for your chosen infrastructure-as-code tool, your templating engine, or your scripting language. They are built for that job. Don’t fight the YAML spec; work with it, and understand its boundaries.
Next time you hit an ‘undefined anchor’ error, don’t just blame YAML. Take a good, hard look at your file structure, your `—` separators, and the specific tool you’re using to process it. That’s where you’ll find the answer, and usually, a much simpler solution than you initially thought.