Back to blog

New Features We Find Exciting in the Kubernetes 1.37 Release

Arsh Sharma · August 27, 2026 · 14 min read

Kubernetes v1.37, “Garhwal,” is named for the Himalayan region in Uttarakhand, India where two of the rivers that eventually become the Ganges begin. The terrain there is so steep that the only way to make it livable is to cut it into terraces, one narrow shelf at a time. That can be said about this release too. Nothing in 1.37 is a single dramatic leap. It’s mostly the same handful of features we’ve been tracking for a release or two now, each one cut a little further into stable ground.

As always, the full release notes cover a lot more terrain than fits in one post, so here’s what we picked out as worth the climb.

Release logo for K8s 1.37
Kubernetes 1.37 (Garhwal) Release Logo

Features Moving to Stable: Ground You Can Stand On

KYAML: A Safer YAML Dialect

YAML’s flexibility is also its curse. Whitespace-significant syntax means a single misplaced space breaks a manifest, and templating tools like Helm make this worse. Helm works by substituting text into a template rather than understanding YAML’s structure, so inserting a multi-line value means counting spaces with functions like indent/nindent so it lines up with the surrounding YAML. Get the whitespace wrong and the manifest either breaks outright or silently parses into something else.

Implicit type coercion is another common trap: an unquoted NO, yes, or 11:00 in a manifest gets silently interpreted as a boolean or a timestamp instead of a string, a classic footgun known as “the Norway problem” (country: NO parses as country: false).

KYAML, reaching Stable in Kubernetes v1.37, is kubectl’s answer to this: a strict subset of YAML that stays compatible with every existing YAML parser while removing the ambiguity. Every string value is double-quoted, so there’s no more guessing whether NO means “the string NO” or “false”. Maps and lists switch to flow style ({} and []), so structure is defined by braces and brackets instead of indentation. You get it through a new kubectl output format. Here’s the same Service requested both ways, starting with the familiar output:

$ kubectl get service hostnames -o yaml

apiVersion: v1
kind: Service
metadata:
  name: hostnames

and now as KYAML:

$ kubectl get service hostnames -o kyaml

{
  apiVersion: "v1",
  kind: "Service",
  metadata: {
    name: "hostnames",
  },
}

Same object, but every string is explicitly quoted and every level of nesting is delimited by {} instead of relying on how far it’s indented.

Because KYAML is valid YAML, existing YAML manifests, Helm charts, and kubectl apply workflows keep working exactly as before. What changes is that when you ask kubectl to hand output back to you, you can (and should!) request the safer dialect instead.

DRA: Device Taints and Tolerations

Dynamic Resource Allocation (DRA): Device Taints and Tolerations made it to the top of the features moving to Beta in our Kubernetes 1.36 recap. Before this feature, if a DRA-managed device like a GPU started overheating or needed maintenance, a driver’s only real option was to pull it out of the ResourceSlice entirely. That stopped it from being used by anyone, healthy running workloads included, with no way to communicate why or let a pod decide whether to keep running on it in a degraded state.

This feature has reached Stable in just one release cycle and applies the same taint and toleration model Kubernetes already uses for nodes to devices instead. A cluster admin can taint a device without touching the driver at all, using a DeviceTaintRule:

apiVersion: resource.k8s.io/v1
kind: DeviceTaintRule
metadata:
  name: degrade-gpu-5
spec:
  deviceSelector:
    driver: nvidia.com/gpu
    device: gpu-5
  taint:
    key: nvidia.com/overheating
    value: "true"
    effect: NoExecute

This taints gpu-5 directly, no driver update required. NoExecute goes further than NoSchedule: it evicts pods already using the device unless their ResourceClaim tolerates that specific taint, as we walked through in the 1.36 post. One thing worth flagging is that there’s still no kubectl extension for managing device taints directly, so DeviceTaintRule objects are the only way to apply them.

Pod Certificates

We discussed Pod Certificates in our Kubernetes 1.35 recap when it reached Beta. Workloads need a way to prove their identity to each other, but the tools Kubernetes offered didn’t fit the job well. ServiceAccount tokens are scoped for talking to the API server, not workload-to-workload identity. The general CertificateSigningRequest API can issue certificates but has no way to get them delivered into a running pod. Teams ended up relying on cert-manager or SPIFFE/SPIRE just to get basic mTLS between pods.

Pod Certificates reaches Stable in Kubernetes v1.37, built around the PodCertificateRequest API and a PodCertificate projected volume. The kubelet generates a private key on the node, requests a certificate on the pod’s behalf, and mounts the resulting chain into the container filesystem, rotating it automatically before it expires:

apiVersion: v1
kind: Pod
metadata:
  namespace: default
  name: pod-certificates-sample
spec:
  containers:
    - name: main
      image: debian
      volumeMounts:
        - name: spiffe-creds
          mountPath: /run/workload-spiffe-creds
      command: ["sleep", "infinity"]
  volumes:
    - name: spiffe-creds
      projected:
        sources:
          - podCertificate:
              signerName: "mysigner.example/spiffe"
              keyType: ED25519
              credentialBundlePath: credentialbundle.pem

In this example, when the pod starts it will find a private key and a signed certificate bundle available at /run/workload-spiffe-creds/credentialbundle.pem which it can use for mTLS authentication.

Between beta and stable, the API gained a stubPKCS10Request field. Some certificate authorities, Vault being a common example, only accept certificate requests in the standard PKCS#10 format. This field provides that format directly, so those CAs can act as signers without anyone having to write a translation layer first.

Features Moving to Beta: The Trail Still Being Cut

Manifest Based Admission Control Config

Admission control has always been backed by API objects: ValidatingWebhookConfiguration, MutatingAdmissionPolicy, and their bindings. A policy defines the rule, and its binding scopes it to specific resources, the same as a Role and a RoleBinding. All of this is stored in etcd which creates a few uncomfortable gaps:

  • During cluster bootstrap, before those objects exist yet, there’s a window where none of your admission policies are actually enforced.
  • Once they do exist, admins have no way to protect the webhook and policy configuration objects themselves from being deleted or modified. This is because webhook admission deliberately doesn’t apply to admission configuration objects themselves.
  • Because everything lives in etcd, a corrupted or unavailable etcd can mean your policies simply fail to load.

Manifest Based Admission Control Config, moving to Beta in Kubernetes v1.37, closes these gaps by letting the API server read admission configuration straight from files on disk instead of only from stored API objects. You configure this per admission plugin, inside the API server’s AdmissionConfiguration:

apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
  - name: ValidatingAdmissionPolicy
    configuration:
      apiVersion: apiserver.config.k8s.io/v1
      kind: ValidatingAdmissionPolicyConfiguration
      staticManifestsDir: "/etc/kubernetes/admission/policies/"

Each plugin’s configuration block is itself a typed object, so it needs its own apiVersion and kind. The manifests in your specified directory load before the API server starts serving requests, so there’s no bootstrap gap. They’re watched on disk and reloaded automatically when changed, and if a reload fails validation, the previous good configuration stays active while the error is logged, rather than admission silently breaking. Because they live outside etcd entirely, protecting them becomes a filesystem and permissions problem rather than a Kubernetes RBAC one, which addresses the self-protection problem too.

Report Last Used Time on a PVC

We first covered Report Last Used Time on a PVC in our 1.36 recap, when it landed as Alpha. When an app is deleted or migrated, its PVCs often stick around, unused but still billing you for storage, and Kubernetes has never had a built-in way to tell how long a given PVC has actually been sitting idle. This KEP fixed that by adding an unusedSince timestamp field to PersistentVolumeClaimStatus in 1.36.

What’s changed in Kubernetes v1.37 as this feature moves to Beta, is the shape of the API. In beta, unusedSince has been replaced with a standard Kubernetes Unused condition instead, following the same type/status/reason/message/lastTransitionTime shape used across the rest of the API:

status:
  conditions:
    - type: Unused
      status: "True"
      reason: NoPodsUsingPVC
      message: No pods are currently referencing this PVC
      lastTransitionTime: "2026-01-20T10:30:00Z"

The PVC Protection controller, the same controller that already tracks when a PVC transitions between in-use and not-in-use, sets status: "True" with reason NoPodsUsingPVC the moment no non-terminal Pod references the PVC anymore, and flips it back to status: "False" with reason PodUsingPVC as soon as one does. lastTransitionTime on that condition is effectively your “unused since” timestamp, so you don’t lose any information compared to the alpha version, but tooling that already knows how to watch Kubernetes conditions, kubectl, controllers, dashboards, can consume this one without any PVC-specific logic.

Features Moving to Alpha: Past Where the Trail Markers End

Scheduler Preemption for In-Place Pod Resize

In-Place Pod Resize reached GA in Kubernetes 1.35, letting you bump a pod’s CPU and memory requests and limits without a restart. That works well when there’s spare room on the node. When there isn’t, say a pod needs more memory than the node currently has free, the resize request just gets stuck with a Deferred status, and the only official guidance is that it “requires manual intervention.” That becomes a problem once you hook this up to something like VPA’s InPlaceOrRecreate mode expecting it to just work: without a way to automatically free up node capacity, workloads either end up migrated to another node anyway, defeating the point of resizing in place, or sit waiting until they hit an out-of-memory kill.

Scheduler Preemption for In-Place Pod Resize, landing as Alpha in Kubernetes v1.37, teaches the scheduler to treat a Deferred resize the way it already treats an unscheduled pod, something worth actively making room for. Deferred resizes get added to the same scheduling queue as new pods, sorted by priority, and the scheduler tries to evict lower-priority pods on that same node to free up the room, the same eviction mechanism it already uses when scheduling a brand new pod, just narrowed down to only that one node, since the pod being resized isn’t going anywhere else.

Not every cluster wants this behavior on by default. Autoscalers in particular may prefer to add a new node rather than evict a workload to free one up. That’s what the new podPreemptionPolicy.disableResizePreemption field on NodeSpec is for:

spec:
  podPreemptionPolicy:
    disableResizePreemption:
      - "autoscaling.k8s.io/cluster-autoscaler"
      - "autoscaling.k8s.io/vpa-updater"

Listing a controller here tells the scheduler not to preempt on that controller’s behalf on this node, and the kubelet sets a PodResizePreemptionDisabled condition on the pod so it’s visible why nothing happened.

CompositePodGroup API

Gang scheduling in Kubernetes today works through a PodGroup: a flat set of pods that all need to be schedulable together, or none of them run. But this model breaks down for large-scale AI training jobs, where the workload itself has internal structure instead of being one flat batch. A big TPU training job might need at least 2 out of 4 “superslices” to be available, and each of those superslices is itself a gang of pods that all need to land within the same physical block of hardware for the interconnect to be fast enough. A flat PodGroup has no way to express that kind of nesting, so today these hierarchical requirements either get hand-rolled by a custom controller or aren’t enforced at all.

CompositePodGroup, landing as Alpha in Kubernetes v1.37, adds a way to describe that structure directly. A CompositePodGroup represents a branch in the tree, and it can have either more CompositePodGroup children or plain PodGroup leaves underneath it, up to 4 levels deep. You don’t hand-write these objects though: a Workload defines the tree as a set of compositePodGroupTemplates, and a workload controller like JobSet or LeaderWorkerSet materializes the actual CompositePodGroup and PodGroup objects from that template. Here’s a simplified version of what that materialized tree looks like for the TPU example from the KEP:

apiVersion: scheduling.k8s.io/v1alpha3
kind: CompositePodGroup
metadata:
  name: tpu-superslice
spec:
  workloadRef:
    workloadName: tpu-training-job
    templateName: superslice
  schedulingPolicy:
    gang:
      minGroupCount: 2
  disruptionMode:
    all: {}
---
apiVersion: scheduling.k8s.io/v1alpha3
kind: PodGroup
metadata:
  name: tpu-shard-1
spec:
  parentCompositePodGroupName: tpu-superslice
  workloadRef:
    workloadName: tpu-training-job
    templateName: shard
  schedulingPolicy:
    gang:
      minCount: 64
---
apiVersion: scheduling.k8s.io/v1alpha3
kind: PodGroup
metadata:
  name: tpu-shard-2
spec:
  parentCompositePodGroupName: tpu-superslice
  workloadRef:
    workloadName: tpu-training-job
    templateName: shard
  schedulingPolicy:
    gang:
      minCount: 64

tpu-superslice is the parent. Its gang.minGroupCount: 2 means at least 2 of its child groups need to be schedulable together for the whole thing to proceed, which is why there are two PodGroup children here, tpu-shard-1 and tpu-shard-2, each pointing back up at the parent through parentCompositePodGroupName. Notice the gang field changes shape one level down: the parent counts child groups (minGroupCount: 2, at least 2 shards need to be schedulable), while each child counts individual pods (minCount: 64, all 64 pods in that shard need to be schedulable). That’s the extra level this API buys you, gang scheduling enforced at both the group-of-groups level and the pod level, at the same time. disruptionMode controls what happens when something needs to be evicted: all: {} means disrupting any part of the group disrupts the whole group together, single: {}, the default when left unset, lets a child be disrupted on its own without taking its siblings down with it.

Once a controller has materialized this tree from the Workload’s templates, none of it schedules anything by itself yet either. The scheduler evaluates the whole tree as one unit: it holds off placing any pods belonging to tpu-superslice until it can find room for at least 2 of its child PodGroups at the same time, with each child group’s own pods still going all-or-nothing together, same as a plain PodGroup on its own. If it can only fit one, none of tpu-superslice’s pods start, which is the guarantee a plain PodGroup already gives you today, just enforced one level higher up the hierarchy.

Frequently Asked Kubernetes Release Questions

How often does Kubernetes release new versions?
Kubernetes ships a new minor version about three times a year, roughly every four months. Each release runs on its own cycle with a rotating release team and a set of fixed checkpoints along the way, like enhancements freeze, code freeze, and test freeze, before it ships.
Where can I read the official Kubernetes release notes?
The full release notes live in the CHANGELOG on the Kubernetes GitHub repository, and kubernetes.io publishes a companion announcement blog for each version. The individual features referenced in those notes are documented in more depth as KEPs (Kubernetes Enhancement Proposals) in the kubernetes/enhancements repo, which is usually the best place to go for the actual design detail behind a given feature.
What do stable, beta, and alpha mean in a Kubernetes release?
They’re feature maturity stages. Alpha features are experimental, off by default, and can change or be removed without notice, so they’re not meant for production use. Beta features are usually on by default and considered well-tested, though their API can still see minor changes. Stable (GA) features are locked in: the API is considered reliable and won’t change in backwards-incompatible ways going forward.
How long does a Kubernetes minor version stay supported?
Kubernetes officially supports the most recent three minor versions at any given time, with patch releases backporting applicable fixes to each of them. In practice that puts a given minor version’s support window at a little over a year from its release, after which you need to upgrade to keep receiving patches.
Why does every Kubernetes release have a codename?
It’s a tradition each release team picks up on their own, not an official project requirement, so the meaning behind a codename varies release to release. The reasoning is usually explained in that version’s own announcement blog on kubernetes.io, chosen by whatever the release team behind that cycle finds meaningful, like a place, a word, or a theme tied to who worked on it.

Takeaways From the Garhwal Release

KYAML, DRA device taints, and Pod Certificates all reach Stable this release after showing up as alpha or beta features in the last post or two we’ve written about Kubernetes releases. Manifest-based admission config and the PVC unused condition close real operational gaps like bootstrapping admission policy before etcd is even reachable and helping reduce costs by telling when a volume actually stopped being used.

The two alpha features point further out. Scheduler preemption for in-place resize and the CompositePodGroup API are both aimed at the same underlying problem: today the scheduler mostly evaluates one pod at a time, independently of the others. Both features push against that, whether it’s freeing up room for a single pod that needs to grow, or coordinating hundreds of pods across a hierarchy of hardware topology for a single AI training run. The two alpha features are past where the trail is marked: early solutions to real problems, still finding their shape. Worth checking back on next release to see how far they’ve been cut in.

What is mirrord?

mirrord is a Kubernetes development platform that lets developers and AI coding agents test code in a production-like environment before deploying it. Your service runs wherever you're working, locally, in CI, or in an agent's sandbox, while mirrord proxies its traffic, environment variables, and files to and from a shared staging cluster, so it behaves as if it were deployed without actually being deployed.

Engineering teams at companies like monday.com, National Australia Bank, and SurveyMonkey use mirrord to iterate and ship faster, while spending less on dev environment infrastructure.

Want to dig deeper?

With mirrord, cloud developers can run local code in the context of their Kubernetes cluster — streamlining coding, debugging, testing, and troubleshooting.