Docker Compose Extension Fields and YAML Anchors, Explained From Zero
How to stop copy-pasting the same ten lines into every service block of a docker-compose.yml file — and let one definition quietly feed all of them at once.
Picture a recipe book where the same “basic pie crust” instructions are printed in full, word for word, at the bottom of twelve different pie recipes. It works, but the moment the author decides to tweak the crust recipe, all twelve copies need to be found and edited individually — and it’s easy to miss one, leaving that pie with a slightly different, now-inconsistent crust. A smarter cookbook would print the crust recipe once, give it a name, and simply say “use the Basic Crust” everywhere it’s needed. YAML anchors and Docker Compose’s extension fields exist to do exactly this for configuration files — letting a block of settings be written once and reused everywhere it applies, instead of duplicated and quietly drifting out of sync over time.
1What Are YAML Anchors, and Why Does Compose Use Them?
The feature belongs to YAML itself, not to Docker — Compose just puts it to very good use.
YAML is the language, Compose is just a reader of it
A docker-compose.yml file is written in YAML, a general-purpose data format used far beyond Docker. Before Compose ever applies its own container-specific logic to that file, a YAML parser first reads the raw text and turns it into structured data. Anchors and aliases are a plain YAML feature, defined by the YAML specification itself, that let one part of a document reference and reuse a block defined somewhere else in the same document — completely independent of what that data will later be used for.
The problem this solves in a real compose file
A typical multi-service application often needs the same settings repeated across several services — the same logging configuration, the same restart policy, the same set of environment variables, or the same resource limits. Written out longhand in every single service block, a ten-line settings group repeated across eight services becomes eighty lines of nearly identical text, and a single needed change means finding and editing all eight copies correctly.
An anchor is like giving a paragraph a footnote number the first time you write it out in full, and an alias is like writing just that footnote number everywhere else it applies. Readers — and in this case, the YAML parser — expand each footnote reference back into the full original text automatically.
Two related but distinct ideas
It’s worth separating two things that often get mentioned in the same breath: YAML anchors and aliases (a general YAML reuse mechanism) and Compose’s extension fields (a Compose-specific naming convention, using keys that start with x-, for storing custom, reusable configuration blocks that Compose itself deliberately ignores). They’re frequently used together — an extension field is often exactly where an anchor gets defined — but understanding them as two separate tools makes both much easier to reason about.
Defines a reusable block
Marks a piece of YAML content with a name so it can be referenced again elsewhere in the same document.
References an anchor
Points back to a previously defined anchor, telling the parser to substitute that content in at this location.
Blends a block into another
A special YAML key that merges an anchored block’s contents into the current block, rather than replacing it wholesale.
Compose’s reuse convention
A top-level key starting with x- that Compose deliberately leaves alone, commonly used to hold anchor definitions.
None of this is Docker-specific magic. The exact same anchor and alias mechanism works in any YAML file — Kubernetes manifests, CI pipeline definitions, and countless other tools all support it, because it’s part of the YAML format itself.
2The Building Blocks: Anchors, Aliases, and the Merge Key
Three small symbols do almost all of the work.
The anchor marker: an ampersand
Placing an ampersand followed by a chosen name directly after a key in a YAML document marks that value as reusable under that name, without changing what the value itself actually is at that location.
The alias marker: an asterisk
Writing an asterisk followed by a previously defined anchor’s name, anywhere else in the same document, tells the parser to substitute in the exact content that anchor points to, as if it had been typed out in full at that spot.
The merge key: double angle brackets
A special mapping key, written as two less-than signs followed by a colon, tells the YAML parser to blend an anchored block’s keys into the surrounding block, rather than simply substituting the anchor’s whole value in as a single nested item. This is what makes it possible to reuse a shared settings block while still adding a few service-specific settings alongside it.
Why the merge key matters so much in practice
A plain alias substitutes in exactly what the anchor defined — useful, but rigid. Real compose files almost always need a shared base plus a few per-service extras layered on top. The merge key is what allows a service to say, in effect, “start with everything from this shared block, then add these specific extra settings on top,” which is the pattern that actually shows up in nearly every real-world use of anchors in a compose file.
flowchart LR
A["Anchor Definition
(&name)"] -->|"referenced by"| B["Alias
(*name)"]
A -->|"blended via"| C["Merge Key
(<<: *name)"]
C --> D["Final Merged Block
(shared settings + local additions)"]
B --> E["Final Substituted Block
(exact copy of anchor)"]
An alias does not create a live, ongoing link back to the anchor. It’s resolved once, at parse time, into a static copy of the anchor’s content. Changing the anchor and re-parsing the file updates every alias that points to it, but there’s no dynamic runtime connection after that.
3How Docker Compose Actually Resolves Anchors
By the time Compose’s own logic runs, anchors are already long gone.
It helps enormously to understand that this entire process happens in two clearly separated stages, handled by two entirely different pieces of software. First, a general-purpose YAML parser reads the raw file and resolves every anchor, alias, and merge key according to the plain YAML specification, producing one fully expanded, anchor-free, in-memory data structure. Only after that is complete does Compose’s own logic run, and by that point, Compose has no awareness that anchors or aliases were ever used at all — it simply sees a normal, fully resolved configuration.
Raw File Read
The compose file’s raw text, including every anchor and alias marker, is loaded from disk exactly as written.
YAML Parsing
A general-purpose YAML parser processes the text and expands every anchor reference and merge key into its final, resolved form.
Fully Resolved Structure
The result is an in-memory structure with no anchors or aliases left in it at all — every reference has already been expanded into real content.
Compose-Specific Interpretation
Compose reads this fully resolved structure, applies its own rules — validating service definitions, interpreting extension fields, filling in defaults — with no knowledge that anchors were ever involved.
This separation explains why extension fields and anchors combine so naturally: Compose simply ignores any top-level key it doesn’t recognize as a real Compose setting, and keys starting with x- are explicitly guaranteed to always fall into that ignored category. That makes them a safe, stable place to define anchors meant purely for reuse, without Compose ever mistaking them for an actual service or configuration setting of their own.
4Extension Fields: Compose’s “x-” Convention
A deliberately reserved space in the file for content Compose promises never to interpret.
Why a dedicated convention exists at all
Anchors could technically be defined anywhere in a YAML file, including directly inside a real service definition. But placing shared, reusable content inside an actual service block is confusing to read and risks Compose mistaking part of it for a real setting on that service. Compose’s specification solves this by formally reserving any top-level key beginning with x- as an extension field — content Compose will always parse structurally, but will never treat as a real setting, no matter what it contains.
What people typically put in an extension field
The most common use is exactly what this tutorial has been building toward: defining an anchor inside an extension field, purely so it can be referenced and merged into multiple real service definitions elsewhere in the same file. Teams commonly create extension fields for shared logging configuration, common environment variable sets, standard resource limits, or repeated health-check settings.
Shared environment variables
A reusable block of environment variables applied consistently across multiple related services.
Shared logging configuration
A single, standardized logging driver and options block reused across every service that should log the same way.
Shared health-check defaults
A common health-check interval, timeout, and retry configuration applied to multiple services with identical monitoring needs.
Shared deployment settings
Common resource limits or restart policies reused across services that should scale and recover the same way.
Extension fields are also sometimes used for a second, unrelated purpose: attaching custom metadata meant for external tooling — a deployment platform or an internal script — that reads the compose file directly and looks for its own recognized x- keys, entirely separate from anchor reuse.
5The Lifecycle of a Reused Configuration Block
From a single definition to several fully-formed service configurations.
sequenceDiagram
participant File as compose.yml
participant Parser as YAML Parser
participant Compose as Compose Engine
participant Services as Running Services
File->>Parser: Read x-common block with anchor
File->>Parser: Read service A with merge key reference
File->>Parser: Read service B with merge key reference
Parser->>Parser: Resolve anchor content
Parser->>Parser: Merge into service A
Parser->>Parser: Merge into service B
Parser->>Compose: Fully resolved, anchor-free structure
Compose->>Compose: Ignore x-common (extension field)
Compose->>Compose: Validate services A and B
Compose->>Services: Apply final merged configuration
What changes if the shared block is edited
Because resolution happens fresh every time the file is parsed, editing the anchor’s content inside the extension field and re-running Compose automatically applies that change to every service that merges it in — with no need to find and update each service individually. This is the entire practical benefit of the pattern: one edit, propagated everywhere it’s referenced, the next time the file is read.
What doesn’t change: per-service overrides
A service that merges in a shared block can still define its own additional keys alongside the merged content, and — depending on how the merge is structured — can override specific values from the shared block for just that one service, keeping the convenience of reuse without losing the flexibility to handle genuine per-service differences.
6Anchors vs Extension Fields vs the “extends” Keyword
Compose actually offers more than one way to avoid repetition — and they solve different problems.
Beyond YAML anchors, Compose also has its own dedicated extends keyword, which lets one service definition explicitly inherit settings from another service definition, even one defined in a completely separate file. It’s easy to assume this does the same job as anchors, but the two mechanisms work at different levels and are suited to different situations.
| Aspect | YAML Anchors + Extension Fields | Compose’s “extends” Keyword |
|---|---|---|
| Mechanism level | Plain YAML feature, resolved before Compose runs | Compose-specific feature, understood by Compose itself |
| Reuse target | Any arbitrary block of settings | Whole service definitions specifically |
| Cross-file reuse | Only within the same YAML file by default | Explicitly designed to reuse a service from another file |
| Flexibility | Very flexible — reuse any structure, not just services | Narrower — focused on service-to-service inheritance |
| Visibility to Compose | Invisible to Compose; fully resolved beforehand | Compose explicitly understands and processes it |
YAML anchors are like a shared ingredient list any recipe in the cookbook can reference. The extends keyword is more like one specific recipe explicitly saying “start with the Margherita pizza recipe, then add these extra toppings” — a relationship deliberately understood by the cookbook’s own recipe-numbering system, not just a shared list of ingredients.
In practice, many teams use both: anchors and extension fields for small, shared settings fragments scattered across several services, and extends for genuinely inheriting from a more complete, related service definition.
7Advantages, Disadvantages, and Trade-offs
Powerful for reducing duplication, but readability has to be actively protected.
Advantages
- Eliminates copy-pasted configuration blocks across many services
- A single edit to a shared anchor propagates everywhere it’s referenced, the next time the file is parsed
- Extension fields give Compose a clean, officially reserved place to hold reusable and custom metadata
- Requires no extra tooling — it’s plain YAML, understood by any standard parser
- Combines naturally with per-service overrides for genuine service-specific differences
Disadvantages / Trade-offs
- Heavy anchor use can make a file harder to read top-to-bottom, since values live elsewhere
- Newcomers unfamiliar with YAML anchors may find the syntax genuinely confusing at first
- Merge behavior for deeply nested structures isn’t always intuitive without testing
- Anchors are resolved only within a single file by default, limiting reuse across multiple compose files
- Overuse can turn a simple compose file into something that requires mentally “unrolling” references to understand
8Security Considerations
This is configuration tooling, not a runtime security boundary — but it still has real security implications.
Propagating a single mistake everywhere at once
The same property that makes shared anchors useful — one change affecting every service that references it — cuts the other way too. A shared block that accidentally includes an overly permissive setting, an unintended exposed port, or a hardcoded secret gets propagated into every single service that merges it in, multiplying a single mistake instead of containing it to one place.
Secrets don’t belong in shared, reused blocks
Sensitive values — passwords, API keys, tokens — should never be placed directly inside an anchor meant for wide reuse, since doing so both spreads that secret across every referencing service and makes the compose file itself a single, high-value target if it ever leaks or gets committed somewhere it shouldn’t. Docker’s own dedicated secrets and environment file mechanisms exist specifically to avoid this pattern.
Treat anchors as high-impact
A change to a widely referenced anchor deserves the same review scrutiny as a change touching every service that uses it — because it does.
Use dedicated secret handling
Reference secrets through Docker’s own secrets or environment file mechanisms rather than embedding them in a reused anchor.
Check the expanded result
Before trusting a compose file with anchors in production, inspect the fully resolved configuration to confirm the merges produced exactly what was intended.
Don’t over-share settings
Group services into logically related shared blocks rather than one giant anchor referenced by every service regardless of whether it actually applies.
Assuming an anchor placed inside an extension field is somehow private or hidden. It is fully visible plain text in the same file, resolved and shared into every referencing service exactly as written — there is nothing confidential about it.
9Validation and Catching Mistakes Before They Reach Production
Because merges happen silently, checking the final result matters more here than in a plain, flat file.
Inspecting the fully resolved configuration
Compose provides a way to output the fully resolved, final configuration exactly as it will actually be used — with every anchor, alias, and merge already expanded — which is by far the most reliable way to confirm that a set of anchors and merges produced the intended result, rather than trying to mentally trace references by eye through the raw file.
CI pipeline validation
Teams commonly add a pipeline step that renders the fully resolved compose configuration on every change, catching unintended merge results or accidental overrides before they ever reach a real environment.
YAML and schema linting
Standard YAML linters can catch malformed anchor or alias syntax early, while Compose-aware validation tools can confirm the resolved structure still matches the expected Compose file schema after all merges are applied.
Diffing resolved output across changes
Comparing the fully resolved configuration before and after a change to a shared anchor makes the true scope of that change — exactly which services were affected and how — immediately visible, rather than relying on manually reasoning through every reference.
10Design Patterns and Anti-Patterns
The difference between a clean, maintainable file and a confusing one comes down to a few habits.
Pattern: one extension field per logical concern
A clean pattern defines a separate, clearly named extension field for each distinct kind of shared setting — one for logging, one for health checks, one for common environment variables — rather than one enormous catch-all block mixing unrelated settings together, which makes it obvious at a glance what each anchor actually governs.
Pattern: descriptive anchor names
Naming an anchor something specific and descriptive, rather than a generic placeholder, makes every alias reference elsewhere in the file self-explanatory without needing to jump back to the definition to understand what’s actually being pulled in.
Problem
Building one giant, catch-all shared anchor containing settings for logging, environment variables, health checks, and resource limits all mixed together, then referencing that single anchor everywhere.
Why It’s Harmful
A service that only needs the shared logging settings ends up also pulling in unrelated environment variables or resource limits it was never meant to have, and a single unrelated edit can unexpectedly affect every service in the file.
Correct Approach
Split shared settings into smaller, logically separate extension fields, and let each service merge in only the specific ones it actually needs.
Problem
Nesting anchors many layers deep, with anchors referencing other anchors referencing other anchors, purely to avoid a few extra lines of repetition.
Why It’s Harmful
Deep chains of indirection make the file extremely difficult for anyone but the original author to follow, and small changes at the bottom of the chain can produce surprising, hard-to-predict effects several layers up.
Correct Approach
Keep anchor structures shallow and favor a small number of clearly named, independently useful shared blocks over long dependency chains between them.
11Best Practices and Common Mistakes
Small habits that keep reused configuration genuinely helpful instead of a maintenance headache.
Place shared anchors near the top
Define extension fields and their anchors near the beginning of the file, so readers encounter the shared definitions before seeing them referenced.
Comment non-obvious merges
Add a short comment near any merge that combines shared settings with meaningful per-service overrides, so the intent is clear without tracing the reference.
Regularly check resolved output
Make reviewing the fully resolved configuration a normal part of changing any shared anchor, not just an occasional troubleshooting step.
Reserve extension fields for genuine reuse
Only extract a setting into a shared extension field once it’s actually duplicated across multiple services — not preemptively for a single service.
Embedding secrets in shared anchors
Placing credentials or tokens directly inside a widely referenced shared block instead of using dedicated secret-handling mechanisms.
Editing an anchor without checking every consumer
Changing a shared block without considering every service that references it, some of which may have relied on the exact previous behavior.
12Real-World and Industry Examples
This pattern shows up constantly wherever teams manage several related services in one file.
Multi-environment compose files
Teams running the same application stack across development, staging, and production environments commonly use shared anchors for the settings that stay identical across environments, layering environment-specific overrides on top for the handful of values that genuinely differ.
Microservice-heavy applications
Applications composed of many small, related services frequently share identical logging, health-check, and restart-policy configuration across most of those services, making this exactly the scenario where anchor reuse pays off the most.
Open-source project templates
Publicly shared example compose files and starter templates frequently demonstrate anchors and extension fields as a recommended pattern for keeping example configurations concise and easy to adapt.
Internal platform tooling
Some organizations build internal tooling that reads custom x- extension fields directly — attaching ownership metadata, cost-center tags, or deployment routing hints to a compose file — entirely separate from the anchor-reuse use case, relying on the same guarantee that Compose itself will never touch those fields.
13Frequently Asked Questions
No. Anchors, aliases, and the merge key are part of the YAML specification itself and work in any YAML file read by a standard-compliant parser, well beyond Docker.
No, an anchor can technically be defined anywhere in the file. Using an extension field is simply the cleanest, officially reserved place to put one, since Compose guarantees it will never try to interpret that content as a real setting.
Not directly — plain YAML anchors only resolve within the single file they’re defined in. Reuse across multiple files is typically handled instead through Compose’s file-combining features or the dedicated “extends” keyword.
The file fails to parse as valid YAML, since an alias without a matching anchor is a structural error the parser catches immediately, well before Compose gets a chance to interpret anything.
Yes. A service can merge in a shared block and also define its own key with the same name directly in its own definition, and depending on ordering, the locally defined value takes precedence — letting most settings come from the shared block while specific ones are overridden per service.
The Compose specification explicitly reserves the “x-” prefix as guaranteed to never be interpreted as a standard setting, precisely so it remains safe for this kind of custom and reusable content indefinitely.
14Summary and Key Takeaways
YAML anchors and Docker Compose’s extension fields solve one of the most common everyday frustrations in managing multi-service applications: the same configuration, copy-pasted again and again, quietly drifting out of sync every time only some of the copies get updated. By relying on plain YAML’s own anchor, alias, and merge-key mechanism — resolved entirely before Compose’s own logic ever runs — and by placing shared definitions inside the officially reserved “x-” extension field convention, teams can define a setting once and have it flow consistently into every service that needs it, while still layering on genuine per-service differences exactly where they belong.
Key Takeaways
- Anchors and aliases are plain YAML, not a Docker feature. They’re resolved by the YAML parser before Compose’s own logic ever runs.
- The merge key blends shared settings into a service rather than replacing the whole block, which is what makes shared-plus-custom configuration possible.
- Extension fields (“x-” keys) are Compose’s officially reserved, never-interpreted space — the natural home for anchors meant purely for reuse.
- Resolution happens fresh on every parse. Editing a shared anchor and re-running Compose propagates that change to everywhere it’s referenced.
- A single mistake in a shared block spreads to every service that merges it in — treat shared anchors as high-impact changes.
- Secrets never belong in a shared, reused anchor. Use Docker’s dedicated secret-handling mechanisms instead.
- Always check the fully resolved configuration rather than trusting the raw file with anchors in it to be self-explanatory.