By the Codebaker DevOps team
With Kubernetes 1.37 “Garhwal”, released on August 26, 2026, KYAML became Stable. It is one of the most-cited features of the release and also one of the most misunderstood: you hear it described as “the new format that replaces YAML”, and it is not.
We have split the article into two parts.
- Part 1, First steps. For those getting started with Kubernetes: what a manifest is, why YAML causes problems and how KYAML avoids them.
- Part 2, Deep dive. For those who have managed clusters for years: rendering rules, the serialization pipeline, comments, multiline strings, Helm, Kustomize, GitOps, CI and an adoption strategy.
The two parts are independent: readers who already have experience can jump straight to the second one.
Part 1: First steps with KYAML
What a Kubernetes manifest is
In Kubernetes you do not tell the cluster what to do step by step. You describe the desired state: “I want three copies of this application, reachable on this port, with this amount of memory”. This description is a manifest, a text file you send to the cluster with a command such as:
kubectl apply -f mia-applicazione.yamlFrom that moment on, Kubernetes works continuously to make reality match what is written in the file.
The format historically used for these files is YAML, a language designed to be readable by a person. Here is a minimal manifest that creates a Pod, that is, the smallest unit Kubernetes runs:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
labels:
app: demo
spec:
containers:
- name: nginx
image: nginx:1.20There are three basic rules:
key: valuedefines a property;- indentation (the spaces at the start of a line) indicates what is inside what:
nameandlabelsare insidemetadata; - the dash
-marks an item of a list:containersis a list that contains a single container.
Where YAML lets you down
YAML is convenient, but it leaves a lot of freedom. People starting out with Kubernetes almost always end up tripping over the same three problems.
Problem 1: two extra spaces change the meaning
In traditional YAML the structure depends solely on spaces. Look at this example:
spec:
containers:
- name: app
image: registry.example.com/app:1.4
resources:
limits:
memory: 512MiWe meant to limit the container's memory, but resources is indented at the same level as containers and therefore ends up inside spec, not inside the container. The file is syntactically correct: no “wrong format” error. The memory limit is simply not applied where you thought it would be. In this fortunate case kubectl will flag an unknown field; in other cases the error goes completely unnoticed.
Problem 2: text that turns into something else
In YAML the quotes around text are optional. The parser tries to guess the type of the value, and sometimes it gets it wrong compared to what you intended:
country: NO # read as "false", not as the country code for Norway
feature: on # read as "true"
version: 1.20 # read as the number 1.2: the trailing zero disappearsThe first case is so famous it has a name: the “Norway problem”.
Problem 3: JSON is not the way out
Kubernetes also accepts JSON, which has none of these problems. But JSON does not allow comments, demands quotes on every key and errors out if you leave a comma after the last element. For files you have to write and read by hand, it is awkward.
KYAML: YAML with stricter rules
KYAML (Kubernetes YAML) is not a new language. It is YAML written always following the same rules:
- objects are always enclosed in curly braces
{ }; - lists are always enclosed in square brackets
[ ]; - text values are always enclosed in double quotes
" "; - every element ends with a comma, including the last one;
- the file begins with
---, a standard YAML marker that indicates the start of a document.
The same Pod as before, in KYAML:
---
{
apiVersion: "v1",
kind: "Pod",
metadata: {
name: "my-pod",
labels: {
app: "demo",
},
},
spec: {
containers: [{
name: "nginx",
image: "nginx:1.20",
}],
},
}What changes in practice:
- spaces no longer matter for the structure. It is the brackets that say what is inside what. Indentation remains for readability, but if you get a space wrong the meaning does not change;
- text stays text.
"NO"in quotes is the string NO, full stop; - you can still add comments, unlike in JSON.
If it looks like JSON to you, you are right: it is very similar. After all, technically, a JSON file is already valid YAML. KYAML sits halfway: stricter than classic YAML, more convenient than JSON.
The questions we get asked most often
Do I need to rename my files to .kyaml?
No. Files stay .yaml. There is no dedicated extension.
Do I need to convert my existing manifests?
No. KYAML is YAML, and traditional YAML keeps working exactly as before. The Kubernetes project has no plans to make it the default format.
Does the cluster need to be upgraded to 1.37 to read KYAML?
No. KYAML is purely a client-side matter. Any version of kubectl and any cluster can read a KYAML file, because it is valid YAML. You only need a recent version of kubectl to generate KYAML.
So what changed with 1.37?
The kubectl ... -o kyaml command is now stable and always available, with no special settings.
The first commands to try
Viewing an existing resource in KYAML:
kubectl get deployment my-app -o kyamlGenerating the skeleton of a new Deployment without touching the cluster, directly in KYAML, and saving it to a file:
kubectl create deployment web --image=nginx:1.27 --replicas=2 \
--dry-run=client -o kyaml > web.yamlApplying it:
kubectl apply -f web.yamlIf you prefer to always see KYAML when you use kubectl get, you can set it as a personal preference:
kubectl kuberc set --section defaults --command get --option output=kyamlThe preference applies only to your own computer: it does not touch the cluster or your colleagues.
In short, for beginners
| Traditional YAML | KYAML | |
|---|---|---|
| What defines the structure | Spaces | Brackets {} and [] |
| Quotes on text | Optional | Mandatory |
| Risk of NO becoming “false” | Yes | No |
| Comments | Yes | Yes |
| File extension | .yaml | .yaml |
| Works with any kubectl | Yes | Yes |
Our advice: learn to read both. Almost all documentation and online examples are in traditional YAML, but using -o kyaml when you explore a cluster gets you used to seeing the structure explicitly and avoids an entire class of errors.
Part 2: A deep dive for those in production
This part assumes familiarity with kubectl, Helm, Kustomize and GitOps workflows. The goal is to understand how KYAML is built, where it really helps and where it can create friction.
Framing: what it is and what it is not
KYAML is defined by KEP-5295 (SIG CLI, authors Tim Hockin and Benjamin Elder), with milestones Alpha in 1.34, Beta in 1.35 and Stable in 1.37. A few points from the KEP are worth keeping in mind:
- it is exclusively a client-side matter. No changes to the API server, no server-side support beyond what already exists for YAML;
- it is an output specification.
kubectlaccepts any valid YAML as input, KYAML included, but does not require the input to be strict KYAML; - among the declared non-goals are changing the Kubernetes YAML library and mandating a migration;
- any component that uses
k8s.io/cli-runtime/pkg/printersandgenericclioptionsinherits KYAML support, so plugins and tools built on cli-runtime get it too.
Evolution of the gate
| Version | Status | Behavior |
|---|---|---|
| 1.34 | Alpha | -o kyaml available only with KUBECTL_KYAML=true |
| 1.35 | Beta | Enabled by default, can be disabled with KUBECTL_KYAML=false |
| 1.36 | Beta | kubectl kuberc drops the alpha prefix |
| 1.37 | Stable | Environment variable removed, -o kyaml always available |
If you have scripts that export KUBECTL_KYAML, with a 1.37 client the variable is simply ignored and you can remove it.
Why YAML is ambiguous precisely in the Kubernetes ecosystem
The library used by Kubernetes is sigs.k8s.io/yaml, which since 1.33 includes forks of go-yaml v2 and v3. The decode path of a manifest is, simplifying:
YAML ──(sigs.k8s.io/yaml)──▶ JSON ──(decoder strict)──▶ oggetto Go tipizzatoImplicit type resolution follows the historical rules of YAML 1.1. The KEP explicitly lists the trickiest cases:
| Unquoted value | Interpreted as |
|---|---|
NO, no, N, YES, yes, Y, On, Off | boolean |
_42, _4_2_ | number |
11:00 | base-60 number |
1.20 | float 1.2 |
0755 | octal integer |
When the error is loud and when it is silent
On the typed fields of the core APIs, coercion usually fails explicitly, because the strict decoder rejects a boolean where it expects a string:
env:
- name: PORT
value: 8080This produces an error like cannot unmarshal number into Go struct field EnvVar...value of type string. Annoying, but visible. The same happens with labels and annotations, which are map[string]string.
The problem becomes silent in three typical situations:
- Helm values.
country: NOinvalues.yamlbecomesfalseeven before the template is rendered. If the template does{{ .Values.country | quote }}, the final manifest will contain"false", perfectly valid and perfectly wrong; - CRDs with permissive schemas. Fields with
x-kubernetes-preserve-unknown-fields: trueor genericobjecttypes accept any type without complaining; - payloads nested inside strings. Application configurations embedded in a ConfigMap and then parsed by the application with a different YAML parser, with different rules.
The IntOrString case
KYAML quotes strings, not everything. Fields like IntOrString, such as targetPort, remain semantically different depending on the type:
ports: [{
port: 80,
targetPort: 8080, # numeric port
}, {
port: 443,
targetPort: "https", # named container port
}],Writing targetPort: "8080" is not equivalent to targetPort: 8080: the string is treated as a port name and fails validation. KYAML makes the type explicit, but it does not spare you from knowing it.
Quantities
resource.Quantity values always serialize as a string. Even if you write cpu: 1, the KYAML output of an object read from the cluster will be cpu: "1". This is not a semantic difference and should not be “fixed”.
The rendering pipeline
To guarantee maximum fidelity to JSON, KYAML does not serialize Go objects directly:
oggetto Go ──▶ JSON ──▶ AST (go-yaml v3) ──▶ KYAMLThe intermediate step through JSON has precise consequences.
The json tags govern everything. omitempty, omitzero, field renames: everything is respected because it goes through the JSON marshaller. The yaml tags are ignored, as already happens with -o yaml. Types that implement json.Marshaler are rendered through it; MarshalYAML() is not used.
Map keys must be strings. YAML allows composite keys (lists, maps) or non-string keys; JSON does not. A YAML with keys of this kind is not convertible to KYAML and the conversion fails. In other words: every KYAML is YAML, but not every YAML can become KYAML.
Map keys are ordered, and the order of struct fields is not guaranteed. kubectl converts everything to unstructured internally and loses the declaration order. Anyone comparing textual output must take this into account.
Anchors, aliases and tags are flattened. Anchors and aliases are materialized, explicit tags (!!str, !!int) and global ones (%TAG) may be simplified, provided the resulting object is identical. If you use anchors to deduplicate blocks in hand-written files, the conversion expands them.
Pointers and interfaces are rendered as null or as the actual value. Unhandled types produce an explicit error instead of ambiguous output.
Formatting rules in detail
Scalars
- integers and floats in their natural numeric representation;
- booleans as
true/false; - strings always in double quotes, with escaping.
Keys
Keys are not quoted, except when they are not “obviously strings” or they coincide with ambiguous words like no. Prefixed label keys (app.kubernetes.io/name) fall among the handled cases. The KEP does not guarantee uniformity: within the same object, quoted and unquoted keys can coexist.
labels: {
app: "hostnames",
"kubernetes.io/service-name": "hostnames",
pod-template-hash: "77b655d8d",
},Commas and brackets
- every list, map and struct always has a trailing comma after the last element, except when the closing bracket is “cuddled”;
- adjacent brackets are cuddled to save vertical space:
[{ ... }, { ... }]; - cuddling breaks when an element has a leading comment or the previous one has a trailing comment;
- empty lists and maps are
[]and{}; - no attempt to put short structures on a single line, no value alignment;
- two-space indentation.
The --- header
It serves to distinguish KYAML from malformed JSON, since both begin with {. According to the KEP, clients from 1.33 onward handle KYAML even without a header; earlier versions need it. If you have environments with dated kubectl in your pipelines, do not remove the header.
Multiple documents
A multi-document KYAML is a normal multi-document YAML: several { ... } blocks separated by ---.
Multiline strings
YAML flow style does not allow the literal blocks | and >. KYAML therefore uses double-quoted strings with flow folding (a backslash at the end of a line, which removes the line break and the leading spaces of the following line), explicit \n for real line breaks and \ to preserve leading spaces.
A ConfigMap in traditional YAML:
data:
nginx.conf: |
server {
listen 80;
location / {
return 200 'ok';
}
}The same content in the KYAML rendering:
data: {
"nginx.conf": "\
server {\n\
\ listen 80;\n\
\ location / {\n\
\ return 200 'ok';\n\
\ }\n\
}\n\
",
},A few observations:
- the extra space on the non-indented lines serves only for visual alignment; YAML discards the leading spaces after a fold anyway;
- the content is byte-for-byte identical to that of the literal block;
- readability degrades noticeably. For ConfigMaps with long configuration files, scripts or certificates, block style remains more practical. It is one of the cases where it makes sense to keep traditional YAML.
Comments: the weak point
KYAML allows comments, but their preservation during conversion depends on go-yaml, which has known limitations in handling comments. The KEP states it openly:
- some comments may end up in different positions or be lost;
- the inline comment of a map, a list or a struct is moved after the closing bracket, because that is the only position where go-yaml finds it again on re-reading;
- without blank lines, a “trailing” comment of an element is indistinguishable from the “leading” comment of the next one.
Practical consequence: before converting a repository, isolate the files with meaningful comments (tuning explanations, workarounds, references to tickets) and check them by hand after the conversion.
Output stability
The KEP guarantees idempotence within the same version: the output of the formatter, passed back through the same formatter, does not change. It does not guarantee byte-for-byte stability across different versions: the rendering may be refined in the future.
Implications:
- do not write tests that compare the output of
-o kyamlbyte for byte against a golden file, unless you pin the client version; - in comparisons use parsing, not text (see below);
- pin the formatter version in CI, as you already do for linters and compilers.
KYAML and Helm
This is where KYAML gives the most concrete advantage, but with an important caveat.
The advantage: no more nindent
In block style, injecting a block requires managing indentation from the template:
spec:
template:
spec:
containers:
- name: app
resources:
{{- toYaml .Values.resources | nindent 12 }}Since JSON is valid YAML flow, in a KYAML template you can inject the output of toJson directly, without worrying about spaces:
spec: {
template: {
spec: {
containers: [{
name: "app",
image: {{ printf "%s:%s" .Values.image.repository .Values.image.tag | toJson }},
resources: {{ toJson .Values.resources }},
}],
},
},
},Two details:
toJsonon a string produces a double-quoted value with JSON escaping, compatible with double-quoted YAML strings. It is more robust thanquotewhen values may contain special characters;- the allowed trailing commas simplify
rangeloops: you no longer have to handle the last element separately.
env: [
{{- range $k, $v := .Values.env }}
{ name: {{ $k | toJson }}, value: {{ $v | toJson }} },
{{- end }}
],The caveat: do not mix styles
The KEP says it explicitly: textually patching KYAML with block-style fragments, as happens in a chart that includes existing helpers, does not work reliably. A chart should be written entirely in one style or the other. Including third-party helpers that emit block style inside a KYAML template is a recipe for parsing errors that are hard to read.
And remember that KYAML does not protect values: the Norway problem hits values.yaml before rendering. If the chart accepts ambiguous strings, quote them in the values or validate them with values.schema.json.
KYAML and Kustomize
kustomize build and kubectl kustomize produce block style. Kustomize reads bases and patches in KYAML without any problem, but the output remains traditional. If you want the final output in KYAML, for example to archive rendered manifests, you need a subsequent formatting step in the pipeline.
Watch out too for hand-written strategic merge or JSON 6902 patches: they are YAML and work in any style, but it is good to keep a style consistent with the base so as not to confuse reviewers.
KYAML and GitOps
Argo CD and Flux compare objects, not text: the desired state is parsed before being compared with the live state. Converting a manifest from block style to KYAML does not generate drift or sync operations, provided the resulting object is identical.
It does generate a huge Git diff, though. Good practices:
- do the conversion in a dedicated commit, with no functional changes;
- add the hash of that commit to
.git-blame-ignore-revs, so thatgit blamekeeps pointing to the real changes; - verify equivalence before merging.
Verifying equivalence
Offline, comparing the parsed form with yq (v4), with sorted keys:
diff \
<(git show HEAD~1:deploy/app.yaml | yq -o=json 'sort_keys(..)') \
<(yq -o=json 'sort_keys(..)' deploy/app.yaml)Online, against the cluster, with kubectl diff, which must return empty output:
kubectl diff -f deploy/The second check is stronger because it also accounts for server-side defaulting and validation.
Exporting resources from the cluster
kubectl get -o kyaml returns the live object, with status, uid, resourceVersion, creationTimestamp and the fields populated by defaults. The managedFields are hidden by default. Before versioning an exported object, remove the runtime fields, by hand or with a plugin like kubectl-neat, and then reformat.
To create clean skeletons, the best path remains --dry-run=client:
kubectl create service clusterip api --tcp=80:8080 \
--dry-run=client -o kyamlConversion tooling
yamlfmt from sigs.k8s.io/yaml
It is the Kubernetes project's tool, used also in the CI verifier of the kubernetes/kubernetes repository:
go install sigs.k8s.io/yaml/yamlfmt@latestIt accepts a file or a directory and writes to stdout, so the output has to be redirected. The KEP provides for flags to choose between “conventional” style and KYAML, so that each file can be verified against the expected style; check --help of the installed version for the exact syntax.
google/yamlfmt
Since version 0.21.0 it includes a formatter dedicated to KYAML. It is the most convenient choice for pre-commit hooks and CI checks, because it already supports verify-without-writing mode and per-path configuration.
yq
It reads KYAML without any problem, being YAML. Useful for going back to block style when needed (yq -P), for querying files and for the equivalence comparisons seen above.
Editors and schemas
The YAML language server of the most common editors works on the parsed document, so JSON Schema validation and autocompletion keep working on KYAML files too.
Compatibility and version skew
| Scenario | Outcome |
|---|---|
KYAML file applied with any kubectl | Works (it is YAML) |
KYAML file without --- with kubectl earlier than 1.33 | Risk of misidentification as JSON |
-o kyaml with a 1.34 client without KUBECTL_KYAML=true | Format not available |
-o kyaml with a 1.35 or 1.36 client | Available, can be disabled via variable |
-o kyaml with a 1.37 client | Always available |
| KYAML output compared byte for byte across different versions | Not guaranteed |
The KEP recommends not using -o kyaml in automation until all the clients involved are upgraded. With 1.37 GA the constraint boils down to checking the version of kubectl present in the CI runner images.
Limits to factor in
- Readability of multiline strings, as seen above.
- Visual density. On very deep objects, the accumulated closing brackets do not aid reading as much as pure indentation.
- Diluted knowledge. One more style to teach the team, in an ecosystem where 99% of the examples are in block style.
- False sense of security. KYAML eliminates syntax and coercion errors, not semantic errors: selectors that do not match, wrong ports, undersized resources all remain possible. Validation with
kubeconformorkubectl apply --dry-run=serverand policies (Kyverno, ValidatingAdmissionPolicy) remain necessary.
Recommended adoption strategy
Our advice, for teams managing multiple clusters and multiple repositories:
- 1
Output before input. Adopt
-o kyamlright away in scripts and automation that readkubectloutput: it is the zero-risk, immediate-benefit use case. - 2
New repositories and new charts in KYAML, with the formatter in verify mode in pre-commit hooks and CI, the formatter version pinned.
- 3
Existing repositories: only if there is a reason. Helm templates fragile on indentation or a history of coercion incidents justify the conversion. Otherwise the cost in diffs and review outweighs the benefit.
- 4
Explicit exceptions. ConfigMaps with long files, vendored third-party manifests and upstream charts stay in block style. Document it in the repository README, so nobody “fixes” them.
- 5
Format-independent validation, always: schema, server-side dry-run and policies.
KYAML does not make YAML obsolete. It removes a category of errors that with Kubernetes we should never have had, without asking you to throw away anything that already works. Adopting it wisely, starting where it pays off most, is the most sensible choice.
Luca Vitali
References
- Kubernetes Blog, Kubernetes v1.37: Garhwal, August 26, 2026
- Kubernetes Blog, How to Pretty-Print Your Kubernetes YAML as KYAML and Why You'd Want To, August 11, 2026
- KEP-5295, Introducing KYAML, a safer, less ambiguous YAML subset / encoding, kubernetes/enhancements
- Kubernetes documentation: KYAML Reference, kuberc
sigs.k8s.io/yamlandgoogle/yamlfmton GitHub
Want a Kubernetes cluster managed the right way?
We design and manage Kubernetes infrastructure on European cloud and on-premise, with clean manifests, GitOps, observability and backups. If you want to talk it over with people who do it every day, get in touch.
Discover Kubernetes & Docker