YAML Anchors, Aliases, and Merge Keys: Reducing Duplication Without Losing Clarity
Learn how YAML anchors, aliases, and merge keys let you factor out repeated configuration in CI/CD pipelines and Kubernetes manifests — without external templating tools.
21 Jul 2026, 09:46 UTC

The duplication problem
When a CI/CD pipeline or a Kubernetes manifest grows, the same resource limits, environment variables, or label sets appear in dozens of jobs or resources. Copy‑pasting works until a change in the base value forces you to edit every occurrence — a classic source of drift and bugs.
YAML 1.1/1.2 gives you three built‑in mechanisms to factor out that repetition: anchors (&id), aliases (*id), and merge keys (<<: *id). They are supported by virtually every parser (PyYAML, SnakeYAML, go‑yaml, libyaml, js‑yaml) and require no external tooling.
Anchors and aliases — the basics
An anchor marks a node so it can be reused later in the same document. An alias inserts a copy of that node wherever you place it.
defaults: &defaults
cpu: "500m"
memory: "256Mi"
env:
- name: LOG_LEVEL
value: info
service-a:
resources: *defaults
service-b:
resources: *defaults
Both service-a and service-b receive an identical resources mapping. Changing the anchor updates both services automatically.
Merge keys — layered overrides
A merge key expands an alias into key‑value pairs that are merged into the current mapping. Later keys win, letting you specialize a base template.
base-deployment: &base
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: main
image: myapp:latest
ports:
- containerPort: 8080
production:
<<: *base
replicas: 5
template:
spec:
containers:
- name: main
resources:
limits:
cpu: "1"
memory: "512Mi"
The production mapping inherits everything from base-deployment but bumps replicas and adds resource limits. The merge is shallow for mappings — nested mappings are replaced unless you merge them explicitly.
Worked example: a GitHub Actions workflow
Below is a compact workflow that builds three micro‑services. The heavy lifting (checkout, cache, test command) lives in an anchor; each job only declares its unique name and Docker image tag.
# .github/workflows/ci.yml
jobs:
build-template: &build-template
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Restore cache
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: maven-${{ runner.os }}-${{ hashFiles('**/pom.xml') }}
- name: Build & test
run: mvn -B verify
service-alpha:
<<: *build-template
name: Build Alpha
env:
IMAGE_TAG: alpha-${{ github.sha }}
service-beta:
<<: *build-template
name: Build Beta
env:
IMAGE_TAG: beta-${{ github.sha }}
service-gamma:
<<: *build-template
name: Build Gamma
env:
IMAGE_TAG: gamma-${{ github.sha }}
Running yq eval 'explode(.)' .github/workflows/ci.yml (requires mikefarah/yq) prints the fully resolved jobs so you can audit the effective YAML before committing.
Trade‑offs and limits
- Parser differences on duplicate keys. GitHub Actions uses a custom parser with last‑key‑wins semantics; other parsers may keep the first value. Test with your target runtime.
- Spaghetti YAML. More than two levels of composition make the effective config hard to trace. For complex variation, prefer a templating layer (Helm, Jsonnet, CUE).
- Cross‑file reuse is not native. Anchors are scoped to a single document. Share values across files with a pre‑processor (e.g.,
yqeval-all, sprig templates, or a small script). - Validation timing. Tools like
kubeconformexpand merge keys before schema validation, whileyamllintworks on the raw YAML. A schema that passes pre‑merge can fail post‑merge if required keys exist only in the base anchor.
Actionable next steps
- Identify a repetitive block in your current pipeline or manifest (resource limits, common labels, checkout steps).
- Extract it into an anchor at the top of the file and replace the copies with
<<: *anchorplus any overrides. - Run the parser‑specific verification:
yq eval 'explode(.)' your-file.yaml– inspect the merged output.- For Kubernetes:
kubectl apply --dry-run=client -f your-file.yamlthenkubectl get -o yamlthe resource to confirm the server‑side result.
- Add a CI lint step (e.g.,
yamllint -c .yamllint.yml) to catch syntax errors early, but remember it won’t validate post‑merge semantics. - Document the anchor/alias convention in your repo’s CONTRIBUTING guide so future contributors know the pattern and its limits.
Used judiciously, anchors, aliases, and merge keys cut duplication dramatically while keeping the source readable. The key is to stop at one or two composition layers and verify the merged result with the same parser that will execute the config.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.