A wall of red and green, one line meant. That is the shape of half the manifest diffs in any busy GitOps repo: someone bumps an image tag, their editor reorders keys or requotes strings along the way, and the reviewer gets a reformatted file to squint through for the single value that actually moved. Kubernetes 1.37 "Garhwal," released August 26, 2026, graduates the fix for exactly that layer of noise to stable: KYAML, a canonical YAML serialization for Kubernetes objects (KEP-5295, SIG CLI) that kubectl can now emit as a supported output format via kubectl get -o kyaml.
The short version: KYAML going stable means both sides of any manifest comparison — the file in git and the object on the cluster — can be rendered byte-identically before diffing, so formatting churn drops out and only real changes remain. Run every manifest through a canonical formatter on the way into the repo, render live objects with -o kyaml on the way out, and the wall of red and green collapses to the one line that matters. But canonical output normalizes formatting, not meaning: server-defaulted fields, status, and list reordering still diff, so the semantic layer — flux diff, drift detection, server-side dry-run — stays. Adopt KYAML as a pre-commit habit and a plan-preview normalization step, not as a replacement for a real diff.
The one-line change hiding in a wall of red and green
Here is the concrete payoff, before any background. Two authors write the same Deployment in block-style YAML. Author A checks in the original; Author B bumps the image from nginx:1.27 to nginx:1.28 and — because their editor sorts keys and normalizes quoting — reformats the file along the way:
# git: author A's original (excerpt)
spec:
replicas: 3
template:
spec:
containers:
- name: web
image: nginx:1.27# PR: author B's bump, reformatted along the way (excerpt)
spec:
template:
spec:
containers:
- image: nginx:1.28
name: web
replicas: 3diff on those two files lights up nearly every line: moved keys, requoted values, restyled list indentation — plus the one real change, buried in the middle. Now pipe both files through the canonical form:
yamlfmt -o=kyaml git/web.yaml > git.kyaml
yamlfmt -o=kyaml pr/web.yaml > pr.kyaml
diff git.kyaml pr.kyamlKYAML renders every mapping in flow style with braces, every list with brackets, every string double-quoted, and map keys sorted — so both files now serialize identically except for the value that actually changed:
spec: {
...
- image: "nginx:1.27",
+ image: "nginx:1.28",
...
},One hunk, one line, the actual change. Everything else in that wall of red and green was serialization noise: two valid spellings of the same object. That is the whole promise of canonical output — and as of 1.37, it is a stable, supported format you can wire into a pipeline instead of a beta experiment.
What "stable" actually standardized
KYAML is not a new language and needs no new parser. As SIG CLI's August 11 pretty-print guide by Kashish Verma puts it, it is an agreed-upon style: a strict subset of YAML where everyone makes the same choices. Every KYAML document is valid YAML, so any kubectl version — not just 1.37 — accepts KYAML as input, and no tool in an existing pipeline needs to change to start reading it.
If your platform generates manifests programmatically, read the emit-side story first: we covered putting the canonical guarantee at the generator boundary when KYAML went stable. This post is the other side of the same coin — the review and drift-detection workflow that consumes those canonical bytes.
The graduation path is short but deliberate: KYAML output debuted as alpha in 1.34 behind a KUBECTL_KYAML=true opt-in, moved to beta in 1.35 enabled by default (still requiring the explicit -o kyaml flag), and is now stable in 1.37. The KEP-5295 rules that reached stability are worth stating precisely, because the diff-hygiene story rests on them:
- Flow style everywhere. Mappings use
{}, lists use[], so structure comes from delimiters instead of indentation depth. - Strings always double-quoted. No more silent type coercion — the classic Norway problem, where unquoted
NOparses as booleanfalse, cannot survive canonicalization. - Map keys sorted. Two spellings of the same mapping serialize to the same bytes regardless of key order in the source file.
- Comments and trailing commas allowed, unlike JSON — it stays a humane config format, not a machine dump.
Two deliberate non-decisions matter for pipeline design. First, upstream has no plans to make KYAML the default output format — you must ask for it with -o kyaml (or set it as your personal default via kubectl kuberc set --section defaults --command get --option output=kyaml on 1.36+). Canonical output is opt-in at every call site, which is exactly what lets you adopt it in one pipeline step without changing every script that parses kubectl get.
Second, the KEP explicitly lists server-side support as a non-goal: KYAML is purely a client-side serialization. The apiserver neither knows nor cares — which brings us to what canonical output does not do.
Diff noise comes in three layers; KYAML kills exactly one
Every noisy manifest diff is a mix of up to three unrelated kinds of churn, and conflating them is why "just normalize it" advice usually disappoints. Name the layers, and each gets its own tool:
| Layer | Example | Killed by |
|---|---|---|
| Formatting churn | Key order, quoting style, block vs. flow, indentation width | KYAML canonical form (yamlfmt -o=kyaml, kubectl -o kyaml) |
| Server-added content | status, creationTimestamp, defaulted strategy/resources, managedFields | Stripping on read (del(.status)), kubectl's existing --show-managed-fields=false default, comparing spec-to-spec |
| Semantically null differences | Reordered env vars, explicit-vs-defaulted values, owned-by-another-manager fields | A real semantic diff: kubectl diff --server-side, flux diff, drift detection with field ownership |
KYAML owns the first row completely and the other two not at all. That division of labor is the honest version of the shorthand "canonical output strips server-side noise": kubectl get hiding managedFields by default is years-old kubectl behavior (the --show-managed-fields flag defaults to false), not something KYAML added, and status plus defaulted fields still appear in -o kyaml output today.
Canonical form guarantees that the same object renders the same bytes — it does not decide which parts of the object are worth comparing. Keep that boundary in mind and the recipe below works; forget it and you will normalize two files, diff them against a live object, and wonder why status is still shouting at you.
The recipe: normalize both sides, then compare
With the layers separated, the pipeline writes itself. There are two comparisons a GitOps-driven fleet actually performs, and canonical form slots into both.
Git-vs.-git: keep formatting churn out of the repo entirely. The cheapest diff is the one that never gets noisy. Google's yamlfmt added a dedicated kyaml formatter in v0.21.0, configurable with two lines in a .yamlfmt file at the repo root and runnable as a pre-commit hook or a Docker image in CI:
# .yamlfmt
formatter:
type: kyamlyamlfmt -dry my-deployment.yaml # preview without modifying
yamlfmt ./k8s/ # convert a whole manifests directoryEnforce it in CI the way you would gofmt: a check step fails the PR if any manifest is not in canonical form, and the reformat commit never mixes with a behavior change. From that day on, every git diff on manifests shows intended changes only — because there is exactly one way to spell anything.
Git-vs.-live: render the cluster side canonically too. For drift checks and plan previews, the live object must pass through the same canonical form as the file, minus the server-added layer. KYAML is valid YAML, so the standard YAML tooling composes:
kubectl get deployment web -o kyaml | yq 'del(.status)' > live.kyaml
yamlfmt -o=kyaml git/web.yaml > git.kyaml
diff git.kyaml live.kyamlThe del(.status) is not optional ceremony — it is the layer-2 handling that KYAML deliberately leaves to you. What remains after both normalizations is the drift that matters: fields where live state genuinely differs from declared state, each attributable to a line in the file. For a fleet running Flux, this same normalize-then-compare shape is what makes flux diff kustomization output reviewable in the first place; canonical form just moves the formatting guarantee left, into the repo, instead of relying on each diff tool to re-derive it.
The semantic layer still belongs to Flux and server-side diff
Canonical bytes answer "are these two renderings identical" — they cannot answer "does this difference matter," because that question needs cluster state KYAML never sees. Three cases stay firmly on the semantic side:
- Field ownership. When Flux's kustomize-controller reconciles, it applies server-side and owns the fields it manages; another controller (a autoscaler writing
replicas, a policy agent injecting sidecars) owns others. A textual diff flags all of them equally. Flux's drift detection — an SSA-based dry-run against live state on every reconcile interval, with optional auto-correction — understands ownership and only reports fields that drifted from your declared intent. Canonical form makes the report readable; it does not make the judgment. - Defaulted-vs.-explicit equivalence. A manifest that omits
strategyand a live object carrying the defaultedstrategy: {type: "RollingUpdate"}are the same deployment, but no text normalization can know the apiserver filled in the blank. Only a server-side dry-run (kubectl diff --server-side,flux diff) expands defaults on both sides before comparing. - List-order semantics. KYAML preserves list order, as it must —
containersandargsare ordered. But some lists (env vars, finalizers) are semantically order-insensitive, and a controller that reorders them will still diff textually. Argo CD users know this asignoreDifferencesconfig; Flux users meet it as drift-detection exclusions. Either way, the fix is semantic configuration, not a stricter serializer.
The practical shape, then, is a stack: KYAML at the bottom guaranteeing byte-stable renderings, kubectl diff / flux diff in the middle expanding defaults and ownership server-side, and drift detection on top turning the residual into alerts and auto-correction. Each layer removes the noise only it can see.
Where canonical output stops: four honest boundaries
Before wiring KYAML into a deploy pipeline's plan-preview step, pin these to the runbook so nobody over-trusts the green diff:
- Live output still carries
statusand defaults.-o kyamlchanges serialization, not content selection. Stripstatus(and comparespec-to-specwhere that is the question) or every live-vs.-git diff drowns in server-written fields. - List order is preserved, not normalized. Reordered-but-equivalent lists still diff. If a controller in your fleet reorders env vars or tolerations, that needs an ignore rule, not a formatter.
- Struct field order is not part of the guarantee. The KEP is explicit:
kubectlconverts through unstructured internally, which loses declared field order; only map keys are promised sorted. In practice output is deterministic, but do not build a checksum-based change detector on byte equality across kubectl versions — diff hunks, yes; hashes, no. - Canonical does not mean minimal. A fully defaulted object renders canonically including its defaults. KYAML answers "same bytes," never "same meaning" — the Norway-problem immunity (quoted strings can't coerce) is about input safety, not diff semantics.
None of these is an argument against adopting it. They are the reason the adoption shape is "formatter plus semantic diff," not "formatter instead of semantic diff."
A better habit, starting at the next PR
KYAML's own announcement frames it as less of a migration and more of a better habit, and for once the framing is exactly right: there is no flag day, no parser upgrade, no apiserver support to wait for. Add the two-line .yamlfmt config, put the canonical-form check in CI, and start rendering live objects with -o kyaml in your drift scripts. The next image-tag bump will show up as the one-line diff it always was — and the review energy that used to go into squinting at reformatted YAML can go into the change itself.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.



