Kubernetes 1.37 HPA Scale-to-Zero: Setup Guide and Cold-Start Gotchas
Kubernetes 1.37 HPA Scale-to-Zero: Setup Guide and Cold-Start Gotchas
Kubernetes v1.37 ("Garhwal") shipped on August 26, 2026, and the change most teams will actually feel is quiet: HorizontalPodAutoscaler scale-to-zero graduated from Alpha to Beta, and it's enabled by default. If you're running batch consumers, queue workers, or GPU inference pods that sit idle for long stretches, you can now let the HPA take a Deployment all the way down to zero replicas and bring it back up when demand returns โ no cron job, no KEDA required for the basic case, no kubectl scale script in a Lambda.
The catch: scale-to-zero only works with metrics that don't require a running Pod, and the first request after a scale-down pays a real cold-start tax. Get either of those wrong and you'll either never scale down or you'll break your SLA the first time traffic goes quiet.
What actually changed in 1.37
Scale-to-zero for the HPA isn't new โ it's been available behind the HPAScaleToZero feature gate since Kubernetes 1.16. What changed in 1.37:
- The feature graduated Alpha โ Beta.
- The
HPAScaleToZerofeature gate is now enabled by default on bothkube-apiserverandkube-controller-managerโ you no longer have to opt in on a self-managed cluster. - The HPA now surfaces a
ScaledToZerostatus condition (True/False) so you can actually observe when a workload is being held at zero, instead of inferring it fromreplicas: 0.
This shipped in the same release as gang scheduling reaching Beta for grouped AI/ML pod placement โ the two features are aimed at the same problem from different ends: gang scheduling controls how a batch of pods starts together, scale-to-zero controls whether they're running at all when there's nothing to do.
Why this matters for cost
The obvious use case is GPU inference and batch/queue workloads that are idle more often than not. A g5.xlarge node sitting there with a Deployment pinned at replicas: 1 "just in case" is pure waste if the workload only sees traffic a few hours a day. Scale-to-zero turns that into pay-only-when-busy, the same value proposition as Lambda, but for workloads that need a real container and can't be rewritten as a function.
Step-by-step: configuring scale-to-zero
1. Confirm the feature gate is actually on
If you're on a managed control plane (EKS, GKE, AKS), check your provider's 1.37 release notes โ feature gate defaults on managed offerings sometimes lag upstream by a version. On a self-managed cluster, check the flags directly:
1kubectl -n kube-system get pod -l component=kube-apiserver -o yaml | grep -i HPAScaleToZero
2kubectl -n kube-system get pod -l component=kube-controller-manager -o yaml | grep -i HPAScaleToZero
If you don't see it explicitly set and you're on 1.37+, it's on by default. On older versions, enable it explicitly with --feature-gates=HPAScaleToZero=true on both components.
2. Pick a metric that survives zero replicas
This is the part people get wrong first. CPU and memory (resource) metrics cannot trigger scale-from-zero โ there's no Pod to measure once you're at zero, so the controller has nothing to evaluate. You need an External or Object metric that exists independently of whether your Pods are running: queue depth, requests-per-second from a load balancer, a Prometheus query result.
The most common real-world setup is Prometheus + the Prometheus Adapter exposing a custom/external metric. If you're already running the CloudWatch managed Prometheus collectors we covered here, this slots in directly on top of that scrape pipeline.
3. Write the HPA manifest
1apiVersion: autoscaling/v2
2kind: HorizontalPodAutoscaler
3metadata:
4 name: order-queue-worker
5 namespace: workers
6spec:
7 scaleTargetRef:
8 apiVersion: apps/v1
9 kind: Deployment
10 name: order-queue-worker
11 minReplicas: 0
12 maxReplicas: 20
13 metrics:
14 - type: External
15 external:
16 metric:
17 name: sqs_approximate_number_of_messages_visible
18 selector:
19 matchLabels:
20 queue: order-processing
21 target:
22 type: AverageValue
23 averageValue: "5"
This holds order-queue-worker at zero replicas while the SQS queue is empty, and scales up roughly one replica per five messages once it isn't. Two things that will bite you if you skip them:
- Don't set
spec.replicasin the Deployment manifest. If it's under GitOps management (Argo CD, Flux) and the Deployment spec pinsreplicas: 1, your CD tool and the HPA will fight over the replica count every sync. minReplicas: 0is rejected on resource-metric HPAs. If you try this with atype: Resource(CPU/memory) metric, the apiserver will rejectminReplicas: 0outright โ you'll get a validation error, not silent bad behavior, which at least fails loud.
4. Verify scale-to-zero actually happened
1kubectl get hpa order-queue-worker -n workers -o jsonpath='{.status.conditions}'
Look for a ScaledToZero condition with status: "True". Don't rely on kubectl get deploy showing 0/0 replicas alone โ that tells you the current count, not whether the HPA is the one holding it there deliberately versus something else having zeroed it out.
Best practices
- Set a realistic readiness probe before you enable this in production. A cold-started Pod that reports
Readybefore its dependencies (DB connections, JIT warmup, config load) are actually up will silently eat requests it can't serve. - Budget for the full cold-start chain: metric detection โ HPA reconcile loop โ Pod scheduling โ image pull โ runtime/app startup โ readiness pass. For a typical containerized service this is single-digit seconds; for JVM or .NET workloads with real startup weight, it can be 10-30+ seconds. Measure it before you ship it.
- Keep images small and pre-pulled where possible โ a multi-stage build that trims a 900MB image to 150MB is a bigger lever on cold-start latency than almost anything else on this list.
- Reserve scale-to-zero for workloads that tolerate a cold start: batch jobs, async queue consumers, dev/staging environments, scheduled reporting jobs, low-traffic internal tools. Don't use it for anything in the synchronous request path of a user-facing API with a tight latency SLA.
Common mistakes to avoid
- Trying to scale-to-zero on CPU/memory metrics. It won't validate, and if you work around it with a wrapper metric, you'll have built something that can't reliably detect "should I wake up."
- No fallback if the external metric source goes down. If your Prometheus Adapter pod (or the metrics pipeline feeding it) is itself unavailable, the HPA has no signal and your workload can get stuck at zero. Monitor the metrics pipeline as carefully as the workload it's scaling.
- Applying it to everything at once. Roll it out to one genuinely idle-heavy workload first, watch the
ScaledToZerocondition and real request latency for a week, then expand. - Forgetting PodDisruptionBudgets interact oddly at zero. A PDB with
minAvailable: 1on a Deployment that's intentionally at zero replicas doesn't block anything by itself, but double-check any external tooling that alerts on PDB violations doesn't false-positive when replicas legitimately hit zero.
Troubleshooting
HPA won't scale below 1 even with minReplicas: 0 set. Confirm the feature gate is actually on for both kube-apiserver and kube-controller-manager โ if only one has it enabled, the apiserver may accept the manifest but the controller won't act on it. Also confirm you're not still on a resource-type metric.
Workload never wakes back up. Check whether the external metric is actually reporting a nonzero value when you expect it to (kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1/namespaces/workers/sqs_approximate_number_of_messages_visible"). If the metrics pipeline itself is stalled, the HPA has nothing to react to.
Scale-up is too slow for the traffic pattern. Scale-to-zero adds an inherent cold-start floor you can't eliminate with HPA tuning alone โ if that floor is unacceptable, this feature is the wrong tool for that particular workload; keep minReplicas: 1 there instead.
FAQ
Does scale-to-zero work with the default type: Resource (CPU/memory) metrics?
No. Only External and Object metric types support minReplicas: 0, because resource metrics require at least one running Pod to sample.
Do I need KEDA for this? No โ as of 1.37, plain HPA with an external/object metric source (like Prometheus Adapter) covers the core scale-to-zero case natively. KEDA remains useful if you want a wider library of pre-built scalers (SQS, Kafka, RabbitMQ, cron) without hand-writing the metrics adapter plumbing yourself.
Is this safe to enable cluster-wide immediately after upgrading to 1.37?
The feature gate being on by default doesn't scale anything to zero by itself โ that only happens on HPAs you explicitly set minReplicas: 0 on. Existing HPAs with minReplicas: 1 or higher are unaffected by the upgrade.
What's the relationship to gang scheduling, also Beta in 1.37? They solve adjacent problems for AI/ML infrastructure: gang scheduling ensures a group of pods for a distributed training job starts together or not at all; scale-to-zero ensures that group isn't holding GPU nodes when there's no job running. Used together, they're aimed squarely at cutting idle GPU spend.
Key takeaways
| Item | Detail |
|---|---|
| Feature | HPA scale-to-zero |
| Status in 1.37 | Beta, enabled by default |
| Feature gate | HPAScaleToZero |
| Requires | External or Object metrics only (not CPU/memory) |
| Config | minReplicas: 0 in the HPA spec |
| Status signal | ScaledToZero condition on the HPA |
| Best fit | Queue consumers, batch jobs, idle-heavy GPU workloads |
| Avoid for | Synchronous, latency-sensitive user-facing APIs |