Skip to main content

KEDA vs Knative vs Sablier: Three Ways to Make Idle Kubernetes Services Cost Nothing

13 min readDora NodaDora Noda
Share
On this page

Fly.io, Render, and Vercel all sell you the same dream: idle costs nothing. Your side project sleeps when nobody visits, wakes on the next request, and you pay for compute only while it serves. It works — until you try to build the same thing on machines you own and discover the part nobody put on the pricing page.

On your own Kubernetes fleet, idle does not cost nothing by default. It costs a full replica set standing around waiting, because the stock Horizontal Pod Autoscaler refuses to scale below one replica. Somebody has to build the 0→1 path: catch the first request to a dead service, hold it, start a pod, then deliver it. That somebody is you.

Here is the verdict up front, with the receipts below:

PathHow it wakesCold-start shapeOperational price
KEDA HTTP add-onInterceptor counts requests, KEDA drives 0→1, HPA takes 1→NFirst request waits for pod + readiness, typically secondsA control-plane operator plus per-service HTTPScaledObject config
Knative activator + KPAIngress routes to activator, which buffers and forwards after scale-upBuffered, no dropped request, but activator is an extra hopA whole Serving layer: new CRDs, KPA semantics, concurrency tuning
Sablier at the reverse proxyTraefik/Caddy plugin intercepts, calls Sablier API, shows waiting pageUser sees a retry/waiting page for one deploy-scale cycleAlmost none: one container + plugin config, no autoscaler rewrite

Pick KEDA if you already run HPA and want per-service opt-in. Pick Knative if you want request-level buffering and concurrency-based autoscaling as the platform default. Pick Sablier if you want preview environments and internal tools to nap without touching your autoscaling stack at all. And whichever you pick, fix your probes first — because as July 2026's CNCF post put it, your health checks are probably waking your services before any real user gets the chance.

Scale-to-zero is rented elsewhere, built here

The rented versions are well documented. Render's free web services sleep after 15 minutes without inbound traffic and take 30–60 seconds to wake. Railway puts idle services to sleep after about 10 minutes and wakes on the next request. Fly.io makes scale-to-zero opt-in per Machine, with sub-second to few-second wakes for small images. Cloud Run — the reference implementation — scales to zero by default and charges per 100 ms of serving time.

Every one of them hides the same three jobs behind one billable abstraction: something must notice the first request to a zero-replica service, something must start the workload, and something must hold the request until the workload is ready instead of returning a 502.

Kubernetes gives you none of those three out of the box. minReplicas on an HPA bottoms out at 1. A Deployment scaled to zero has no pod for kube-proxy or your ingress to route to, so the first request fails unless an intermediary catches it. And kubelet liveness/readiness probes, cloud load-balancer health checks, Prometheus blackbox exporters, and uptime monitors all keep knocking on doors that are supposed to stay shut — each knock looks like traffic to a naive scaler.

So the real question for a self-hosted fleet is not "should idle cost zero" but "where do you put the interception layer, and what does it cost you every day after the demo." The three serious answers place it in three different spots: the metrics pipeline, the serving data plane, or the reverse proxy you already run.

KEDA HTTP add-on: event-driven 0→1, HPA for the rest

KEDA's core insight is a division of labor. KEDA handles 0→1 and 1→0; the HPA handles 1→N. A ScaledObject with minReplicaCount: 0 watches an event source, and when its activationThreshold trips, KEDA scales the target directly through the scale subresource. Once at least one pod exists, the HPA it manages takes over normal scaling. Google's own GKE guidance now recommends exactly this shape for scale-to-zero workloads.

The HTTP add-on extends that model to raw request count. An interceptor counts in-flight requests per host, a scaler turns those counts into metrics, and an operator wires up HTTPScaledObject resources. Routing stays the same, and per-service opt-in is one CR:

yaml
apiVersion: http.keda.sh/v1alpha1
kind: HTTPScaledObject
metadata:
  name: preview-api
spec:
  hosts: ["preview-042.example.com"]
  target:
    kind: Deployment
    name: preview-api
  scaledownPeriod: 300
  replicas:
    min: 0
    max: 20

What it buys you is precision. Each tenant gets its own metric and scale-down window, with normal HPA behavior above one replica. What it costs is an always-on metrics path plus one more CRD family. Cold starts are honest but visible: the first request waits for pull, boot, and readiness — typically 20–40 seconds per operator guidance. Fine for previews, painful for latency-sensitive APIs.

KEDA wins when you already run standard Deployments with HPA and want scale-to-zero as a per-service upgrade rather than a platform rewrite. It loses when you have hundreds of tiny idle services, because every one of them needs its object, its interceptor routing rule, and its scale-down tuning.

Knative activator: buffer the first request instead of dropping it

Knative Serving takes the opposite approach: make scale-to-zero the platform default and put a real data-plane buffer in front of everything. When a Knative Service has pods, traffic goes straight to them. When it has scaled to zero after the idle window (60 seconds by default), the ingress routes incoming requests to the activator instead. The activator holds the connection open, signals the KPA autoscaler to scale up, and forwards the queued request once a pod passes readiness. The caller sees a slow first response, not an error.

The autoscaler itself is the second half of the deal. KPA scales on concurrent in-flight requests per pod, not CPU percentage — a far better signal for web workloads, where "requests waiting" predicts saturation earlier than "CPU hot." Target concurrency, burst capacity, and scale-to-zero grace periods are per-revision annotations, so tenants get sane defaults without learning HPA math.

The price is architectural commitment. Knative brings its own Service, Revision, and Route CRDs, its own networking layer, and an always-on activator tier. The extra hop is negligible once warm but load-bearing during wakes: tune burst capacity wrong and every request pays buffering latency at steady state.

Choose Knative when you want Cloud Run semantics for every tenant by default — deploy a container, get a URL, sleep when idle — and you are willing to standardize the whole fleet on its Serving abstractions. Do not choose it as a sidecar to an existing HPA fleet; running two autoscaling philosophies on the same workloads is how you get 3 a.m. pages about dueling scalers.

Sablier: wake at the reverse proxy, touch nothing else

Sablier refuses the entire autoscaler debate. It is one API container plus a plugin for the reverse proxy you already run — Traefik, Caddy, Nginx, Envoy — and it works at workload granularity, not request-metric granularity. Mark a workload with "start on demand, stop after N idle minutes," point the proxy plugin at the Sablier API, and the flow is mechanical: request arrives for a sleeping workload, the plugin intercepts it, Sablier scales the Deployment (or Docker container, or Swarm service) back up, and the caller sees a configurable waiting page until readiness passes, then retries into a running backend.

There is no control plane to adopt and no new Service abstraction. A Traefik middleware annotation enrolls one more host; unenrolled hosts behave exactly as before. That makes Sablier the cheapest of the three to trial, and the natural fit for what dominates idle capacity on real fleets: per-PR previews, internal dashboards, weekly QA environments.

The tradeoff is honesty about the wake experience. Sablier does not buffer and forward the original request the way the Knative activator does; it parks the caller on a waiting page and lets the retry land on the warmed backend. For a human clicking a preview link, that is perfectly fine. For machine-to-machine webhooks or latency-sensitive APIs, it is a behavior change your callers will notice. And because Sablier thinks in "workload sleeping vs running" rather than request concurrency, it will not autoscale a hot service from 2 to 20 replicas — pair it with HPA for the 1→N half if a workload needs both.

Choose Sablier when your problem is "fifty idle things burning RAM on owned boxes" rather than "one hot thing needing smarter autoscaling." It is the only one of the three you can roll out on a Friday without retraining the team on new CRDs.

The gotcha that eats all three: health checks wake the dead

Here is the mandatory pre-reading: the CNCF/KubeElasti team's July 2026 "Your Kubernetes health checks are accidentally waking your services." Its finding is familiar to anyone who has watched a zero-replica service refuse to stay at zero: teams configure scale-to-zero, achieve 0% of the savings, and blame the scaler — when their own monitoring stack never lets anything stay idle.

The wakers form a taxonomy worth auditing line by line:

  • Cloud load-balancer health checks. ALB, GCP LB, Azure App Gateway all require periodic checks to route safely. They are frequent, relentless, and have no concept of "intentionally idle."
  • Kubelet and ingress probes. Liveness/readiness definitions get mirrored at the ingress layer; a zero-replica service registered with an uptime monitor triggers wake-on-probe.
  • Mesh and observability probes. Istio/Linkerd sidecar checks, Prometheus blackbox exporter scrapes, and internal developer-platform availability loops all assert liveness continuously. None distinguishes degraded from deliberately idle.

KubeElasti's answer — a ProbeResponse rule set evaluated by the resolver before deciding to scale — is worth copying whatever stack you run: match synthetic traffic by method, path prefix, headers, or query params and answer it directly with a canned 200, so only real traffic wakes the workload. When the service has active pods, the resolver leaves the path entirely; probe rules cost nothing at steady state.

Run this audit before you tune a single scaler:

  1. List every prober: cloud LB check path and interval, ingress annotations, blackbox scrape targets, mesh sidecar behavior, uptime-monitor URLs.
  2. For each, record method, exact path, identifying headers, and frequency.
  3. Give synthetics their own answer path: a resolver/proxy rule (or a tiny always-on responder) that returns what the real service would return healthy — exact status and body if monitors validate payloads.
  4. Separate kubelet readiness (which gates real traffic to real pods) from edge probing (which must never wake anything). Never point an edge prober at a path that scales.
  5. Re-verify after every ingress or mesh upgrade; defaults for probe mirroring change silently.

Skip this step and your choice of KEDA vs Knative vs Sablier does not matter. Every one of them will faithfully wake your fleet every 30 seconds, on schedule, forever.

What idle-to-zero actually costs on owned hardware

Put the three side by side on the axes the title promised — cold start, HPA config, probe hygiene — for a Cluster-API-managed Hetzner fleet packing many small tenant services onto fixed boxes:

AxisKEDA HTTP add-onKnative activatorSablier
Cold-start latencySeconds: pull + boot + readiness on the waking requestSeconds, but buffered: caller waits, request survivesOne waiting-page cycle, then retry lands warm
Config surfacePer-service HTTPScaledObject + interceptor routing; HPA unchanged above 1Per-revision concurrency annotations; KPA replaces HPA semanticsOne Sablier container + proxy plugin; per-workload idle timeout
Probe hygieneRequired: exclude probe paths from request-count metricsRequired: activator must not treat probes as concurrencyRequired: probe rules answered at proxy, never waking
Always-on overheadInterceptor + scaler + operatorActivator tier + controller + networking layerSablier API container only
Multi-tenant fitBest per-service opt-in on a shared HPA fleetBest whole-platform default with per-tenant concurrencyBest heterogeneous sprawl: previews, tools, QA
Failure modeMis-tuned thresholds flap 0↔1Burst-capacity misconfig adds permanent latencyNon-buffered wake breaks machine callers

Now the fleet math that justifies any of it. Take fifty preview environments at 0.5 vCPU and 1 GB each, idle 20 hours a day. Always-on, that is 25 vCPU and 50 GB of reserved-but-useless capacity — most of a dedicated Hetzner box burned on nothing. At zero when idle, the same fifty cost one Sablier container, one interceptor or activator tier, and a few wake-seconds per human visit. The breakeven is not subtle: if fewer than a third of your workloads are warm at any moment, the interception layer pays for itself many times over. If nearly everything is warm all the time, skip all three and spend the effort on bin-packing instead.

Decision guide, stated plainly: already on HPA and want opt-in naps, pick KEDA. Standardizing a new tenant platform on request-driven semantics, pick Knative. Drowning in idle previews and internal tools on fixed hardware, pick Sablier first and revisit the other two when a workload outgrows "asleep or awake." Most real fleets end up with two: Sablier for the long tail of idle things, plus KEDA or Knative for the serving path that needs real autoscaling.

The boring part is the point

Scale-to-zero looks like a scaling feature and behaves like a traffic-classification feature. The scaler is the easy third of the job; telling real requests from synthetic ones, holding the real ones kindly, and answering the synthetic ones without waking anything is the other two thirds. Get probe hygiene right and any of the three paths pays off on owned hardware within weeks. Get it wrong and you have added a wake-on-LAN button that your own monitoring presses every 30 seconds.

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.

Sources

  • KEDA HTTP Add-on docs — HTTP-based autoscaling including scale-to-zero via HTTPScaledObject, interceptor, scaler, and operator (keda.sh / add-on overview)
  • Google Cloud — Scale to zero on GKE with KEDA, and the GKE KEDA tutorial using HTTPScaledObject (blog / docs)
  • Knative Serving architecture — activator request buffering, KPA scale-from-zero, concurrency-based autoscaling (docs)
  • Sablier — start workloads on demand via Traefik/Caddy/Nginx/Envoy plugins, waiting page until ready (sablierapp.dev / plugin docs)
  • CNCF, July 29 2026 — "Your Kubernetes health checks are accidentally waking your services. Here's the fix." (KubeElasti ProbeResponse: answer synthetics at the resolver, wake only on real traffic) (cncf.io)
  • Render docs — free web services sleep after 15 min idle, 30–60s wake; Railway docs — ~10-min idle sleep; Fly.io Machines scale-to-zero semantics

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex