6 min readJust now
–
Kubernetes is here, and it is not “the future” anymore — it is the present. Most of us describe our clusters as a desired state, spread across dozens or hundreds of YAML files. That approach scales well, it works great with Git, and it makes collaboration between developers, SREs, and platform teams surprisingly natural.
But there is a dark side: those “many YAML files” are full of hidden relationships, copy‑pasted fragments, and repeating patterns like names, URLs, and references. Maintaining them by hand quickly turns from “declarative zen” into “YAML archaeology”.
Helm helps a lot. It lets you generate these manifests from parameterized templates, so instead of editing raw manifests you work with values files and charts. If you add ArgoCD on top, you get…
6 min readJust now
–
Kubernetes is here, and it is not “the future” anymore — it is the present. Most of us describe our clusters as a desired state, spread across dozens or hundreds of YAML files. That approach scales well, it works great with Git, and it makes collaboration between developers, SREs, and platform teams surprisingly natural.
But there is a dark side: those “many YAML files” are full of hidden relationships, copy‑pasted fragments, and repeating patterns like names, URLs, and references. Maintaining them by hand quickly turns from “declarative zen” into “YAML archaeology”.
Helm helps a lot. It lets you generate these manifests from parameterized templates, so instead of editing raw manifests you work with values files and charts. If you add ArgoCD on top, you get a very neat flow: Helm renders manifests, ArgoCD watches a Git repository, computes the diff against the cluster, and applies the new desired state.
Press enter or click to view image in full size
At that point everything looks perfect on a slide. All you “just” need to do is keep your configuration files in sync across environments. Dev, UAT, Prod — same charts, different values. How hard can it be?
Turns out: very hard.
Imagine multiple development teams continuously pushing changes for weeks. On top of that, your SRE team is tweaking health checks, replica counts, and resource limits directly in higher environments. Over time it becomes almost impossible to keep track of what should be promoted from DEV to UAT, or which changes from UAT should be carefully applied to PROD after successful testing.
This is where a lot of teams fall back to “open both folders in the IDE and eyeball the diffs” — and that simply does not scale.
Why I needed a delta tool for environments
I reached a point where I realized I needed a dedicated delta tool — something that:
- Understands YAML structurally, not as plain text.
- Lets me say “these fields must never be synced between environments”.
- Knows how to apply environment‑specific transformations automatically.
- Produces readable, consistent output that people can actually review.
That is why I built HelmEnvDelta: an environment‑aware YAML delta and sync tool for GitOps workflows, focused on the space between your environments, not between your repo and the cluster.
You configure it once, and then you can reuse that configuration over many iterations of your promotion flow. The idea is that your sync rules become part of your platform, not an ad‑hoc manual process.
Below is how it works, using concepts from the tool and concrete examples from the project’s README and examples.
Source, destination, include, exclude, prune
The starting point is simple: define a source environment and a destination environment. Think ./helm/uat → ./helm/prod, or ./gitops/nonprod → ./gitops/prod.
You can then tune which files are in scope:
- include / exclude: glob patterns for files you want to process or ignore (typically
**/*.yaml, but any text file works). Non‑matching files are simply copied over. - prune: optionally delete destination files that no longer exist in the source. This is powerful and a bit dangerous — it can be used to remove Kubernetes resources that were removed from lower environments, so it belongs behind a dry‑run and a review.
This gives you a basic, structured “rsync for YAML”, but it gets interesting when you start telling HelmEnvDelta what not to touch.
SkipPath: ignore the fields that must stay different
For me, the most exciting part of the configuration is skipPath. Instead of diffing raw text, HelmEnvDelta parses YAML and compares it logically: it does not care about the order of keys or how arrays are formatted.
skipPath lets you specify JSONPath‑style paths that should be completely ignored during comparison and sync. These are the fields that are allowed – or required – to differ between environments:
metadata.namespacein production should not be overwritten from UAT.spec.replicasmight be handled by SREs and must stay under their control.- Health checks, timeouts, or resource limits can be intentionally different in PROD.
When a path is listed in skipPath, that part of the YAML is effectively “frozen” on the destination side. You will not see it in diffs, and HelmEnvDelta will not touch it when syncing, even if the source is different.
This is a small idea with a big impact: developers can promote features and configuration changes without accidentally stomping on environment‑specific operational settings.
Transform: environment-aware rewrites for values and filenames
The next layer is transformations. Any value or file name can go through a rule‑based transformation step. This is where environment‑specific differences are handled cleanly instead of via manual search‑and‑replace.
Typical examples:
- File names:
main-uat.yamlin UAT becomesmain-prod.yamlin PROD. - URLs:
https://service-uat.example.com→[https://service\.example\.com](https://service.example.com.). - Connection strings: different database hosts, Kafka clusters, or S3 buckets per environment.
You define simple “from → to” rules, often regex‑based, and HelmEnvDelta applies them consistently whenever it copies or compares content. When a new file is created in the destination, it is created after these transforms, so you do not get noisy diffs later.
The nice property is that transforms are applied in a controlled, repeatable way — no more “I hope my IDE replaced all instances correctly”.
Output formatting: making YAML human-friendly again
Once the content is correct, there is still the question of readability. HelmEnvDelta includes an output formatting layer so that the resulting YAML is pleasant to read and easy to review.
You can control things like:
indent: standard indentation level.quoteValues: force certain values (for example env vars) to always be quoted.keySeparator: insert blank lines between logical sections to improve readability.keyOrders: enforce a logical key order, such asapiVersion,kind,metadata,spec.arraySort: sort arrays (for exampleenventries) by a key likename.
The first time you apply strict formatting, the diff might be large. After that, everything stays consistent, and reviewers can focus on real configuration changes instead of noise from reordering and spacing.
For anyone who has ever tried to review a Helm‑rendered manifest where half the changes are just indentation, this is a huge quality‑of‑life improvement.
Config inheritance: don’t duplicate your rules
If you are thinking, “this is a lot of configuration to repeat for every environment pair”, there is good news. HelmEnvDelta supports config inheritance via an extends mechanism.
You can define shared rules in a common-config.yaml – include/exclude patterns, skip paths, formatting rules, etc. Then, environment‑specific configs (like dev-to-uat.yaml, uat-to-prod.yaml) can extend the common base and only override what is different.
Under the hood:
- Objects are deep‑merged.
- Arrays (like
skipPath) are concatenated.
This means you can build a multi‑stage promotion chain with a small, maintainable set of configs instead of a copy‑pasted mess.
Git and the rest of the workflow
One deliberate design decision: HelmEnvDelta does not perform Git operations. It reads from your working tree, writes changes, and stops there.
The reasons are simple:
- Git workflows differ a lot between teams (branching model, commit rules, approvals).
- As soon as a tool starts pushing commits, it becomes responsible for a much wider set of concerns.
- Keeping Git out of scope keeps the tool focused and predictable.
You run helm-env-delta, then you use your normal Git commands, open a PR, let ArgoCD or Flux do their job, and keep all the usual checks and balances in place.
The CLI still helps with discoverability: --help shows all options, and the repository contains multiple ready‑to‑run examples covering inheritance, stop rules, multi‑env chains, and prune behavior.
From “diff hell” to repeatable promotions
For a long time, environment‑to‑environment diffing in Kubernetes felt like a necessary evil: either you accepted the risk of manual syncing, or you tried to bend Helm/Helmfile/Kustomize into solving a problem they were never designed for.
HelmEnvDelta is an attempt to make that space first‑class:
- Structural YAML diffs instead of line‑based noise.
- Explicit rules for what must never be synced.
- Automatic transforms for environment‑specific values and file names.
- Consistent YAML output that is pleasant to read.
- Reusable configuration with inheritance instead of repetition.
npm i -g helm-env-delta
If you are living in the same world — Kubernetes, Helm, GitOps, multiple environments and lots of YAML — this kind of tool can turn a painful, error‑prone promotion process into something boring and repeatable. And in infrastructure, “boring and repeatable” is often exactly what you want.