Back to Blog
Architecture10 min read2026-07-09

KEDA Scale-to-Zero Explained

Kubernetes can't scale a Deployment to zero on its own. KEDA can — driving replicas down to nothing when idle and back up on the first request. Here's how it works and the trade-offs.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Why scaling to zero is hard in plain Kubernetes

Kubernetes ships with the Horizontal Pod Autoscaler (HPA), which scales Deployments based on CPU or memory. But HPA has a floor it can't cross: its minimum is 1 replica. It scales *down* to 1, never to 0. That means an idle service still burns a pod's worth of CPU and memory around the clock.

For production services handling constant traffic that's fine. For the long tail — dev environments, preview deploys, internal tools, hobby projects, services with bursty or rare traffic — running 24/7 for traffic that arrives twice a day is pure waste. Scale-to-zero fixes that: zero replicas when idle, spin up on demand. That's where [KEDA](https://keda.sh/) comes in.

What KEDA is

KEDA (Kubernetes Event-Driven Autoscaling) is a CNCF project that extends Kubernetes autoscaling in two key ways:

  1. 1It scales on events, not just CPU/memory. Queue depth, HTTP request rate, Kafka lag, cron schedules, database query results — 60+ built-in *scalers*.
  2. 2It can scale to and from zero. Unlike HPA, KEDA's minimum is 0.

Under the hood KEDA doesn't replace HPA — it *drives* it. KEDA's operator watches your event source and, crucially, manages the 1↔0 transition that HPA can't, while delegating 1↔N scaling to a managed HPA it creates.

The scale-to-zero mechanism

The interesting part is how a service at zero replicas comes back. If there are no pods, who handles the first request?

The pattern (used by KEDA's HTTP add-on and similar designs) is an activator/interceptor sitting in the request path:

Request arrives
   -> interceptor sees target has 0 replicas
   -> interceptor holds the request, signals KEDA to scale up
   -> KEDA scales Deployment 0 -> 1
   -> new pod becomes Ready
   -> interceptor forwards the held request to the pod
   -> subsequent requests go straight through

The first request after idle pays the cold-start cost — pod scheduling, image pull (if not cached), container start, app boot. After that, the service is warm and behaves normally until it goes idle again and scales back to zero.

A basic ScaledObject for HTTP-style scaling looks like:

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: my-app
spec:
  scaleTargetRef:
    name: my-app          # the Deployment
  minReplicaCount: 0      # the magic: zero when idle
  maxReplicaCount: 10
  cooldownPeriod: 300     # wait 5 min of idle before scaling to zero
  triggers:
    - type: prometheus
      metadata:
        serverAddress: http://prometheus.monitoring:9090
        query: sum(rate(http_requests_total{app="my-app"}[1m]))
        threshold: "1"

The cooldownPeriod matters: scale down too eagerly and you cold-start constantly; too slowly and you waste resources. 5 minutes is a reasonable default.

KEDA vs HPA at a glance

CapabilityHPAKEDA
Minimum replicas10
Scale on CPU/memoryYesYes (wraps HPA)
Scale on events (queue, HTTP, cron, Kafka…)NoYes (60+ scalers)
Scale-to-zeroNoYes
First-request handlingN/AActivator holds request

The trade-offs

Scale-to-zero is a genuine win, but it's a trade, not a free lunch:

  • Cold-start latency. The first request after idle is slow. For a user-facing service this is the cost you're accepting in exchange for not paying for idle compute. (We cover reducing cold starts in a dedicated post — image size, warm pools, lighter runtimes.)
  • Not for everything. A service with steady traffic never goes idle, so scale-to-zero gives you nothing and adds an interceptor hop. Use it for bursty/intermittent workloads.
  • Stateful gotchas. In-memory caches and warm connection pools die when the pod scales to zero. Keep state external (Redis, the database) so cold starts are stateless.
  • Connection handling. Long-lived connections (WebSockets) and scale-to-zero don't mix well; the service can't go idle while a connection is open, and reconnection logic must tolerate cold starts.

Where this fits on a platform

Scale-to-zero is the economic engine behind a generous free tier. If free-tier apps ran 24/7, the math wouldn't work; scaling idle apps to zero is what makes "5 web services for $0" sustainable.

That's exactly how PandaStack uses it. Free-tier apps use KEDA scale-to-zero on spot/preemptible nodes inside a gVisor sandbox — idle apps cost nothing to run, and the first request after idle pays a cold start before the app serves normally. It's the honest trade we make explicit in our limits: free-tier apps cold-start on preemptible nodes. For production traffic you'd typically run on a tier without scale-to-zero so there's always a warm replica.

When to reach for it

Use KEDA scale-to-zero for dev/staging environments, preview deploys, internal tools, scheduled jobs, queue consumers, and any workload whose traffic is bursty enough that idle time dominates. Keep steady-traffic production services on a normal min-replica HPA. And whatever you scale to zero, design it stateless so the cold start is a latency event, not a correctness one.

References

  • [KEDA — official documentation](https://keda.sh/docs/)
  • [KEDA — HTTP Add-on (scale-to-zero for HTTP)](https://github.com/kedacore/http-add-on)
  • [KEDA — Scalers catalog](https://keda.sh/docs/latest/scalers/)
  • [Kubernetes — Horizontal Pod Autoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/)

Want scale-to-zero without writing a single ScaledObject? PandaStack's free tier runs apps with KEDA scale-to-zero automatically — try it: [dashboard.pandastack.io](https://dashboard.pandastack.io)

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Architecture

Browse all Architecture articles →

See also