Skip to content

Scale to Zero

Preview environments are incredibly powerful, but they can be expensive if they run 24/7. Most pull requests are reviewed intermittently—a few minutes of activity followed by hours or days of idle time.

Diverge solves this by allowing your preview environments to scale to zero when they aren’t receiving traffic.

  • Massive Cost Savings: 50 PRs with always-on pods cost ~$1,200/month. With Knative scale-to-zero, that same workload costs ~$15/month (illustrative estimate based on 50 always-on pods vs. ~3 concurrently active).
  • Resource Efficiency: Fit hundreds of preview environments on a small cluster.
  • Fast Startup: Previews wake up automatically when a reviewer opens them (startup is typically ~5 seconds, but varies with image size, scheduling, and readiness probe configuration). The Activator buffers requests during startup, though requests may time out if pod startup exceeds the client or proxy request timeout.

Diverge integrates with Knative Serving and its own Activator Proxy to manage the lifecycle of your pods:

  1. Idle Detection: Knative monitors traffic to your preview services. When no requests arrive for a configurable period, Knative scales the deployment down to 0 replicas.
  2. Wake Up Flow: When a new request arrives, it hits the Activator Proxy.
  3. Queue & Scale: The proxy holds the request, triggers a scale-up of the service to 1 (or more) replicas, and waits for the pod to become ready.
  4. Forward: Once ready, the proxy forwards the queued request to the newly running pod. Subsequent requests go directly to the pod.

The Activator Proxy (activator-proxy) is a specialized binary that sits in front of preview environments.

  • Smart Routing: Routes to ready pods directly when available to minimize latency.
  • Fallback to Knative / Scale-Up: If pods are scaled to zero, it triggers a scale-up of the deployment (or falls back to the Knative/KEDA activator) to wake the service.
  • Preview Wake-Up Splash Page (--wake-splash): Serves an auto-refreshing 202 Accepted HTML splash screen to browsers during cold starts instead of hanging the browser tab, while keeping API/programmatic clients buffered transparently.
  • Header Injection: Injects the X-Preview-Env header to ensure routing rules continue to work.
  • Efficient State Tracking: Uses a shared informer to efficiently track pod states across the cluster.

Preview Wake-Up Splash Page (--wake-splash)

Section titled “Preview Wake-Up Splash Page (--wake-splash)”

When a reviewer clicks a preview URL in a browser while the preview pod is scaled to zero, waiting several seconds on a blank loading spinner can feel broken.

The activator-proxy supports a configurable Wake-Up Splash Page (controlled globally via --wake-splash, default false, or per request via the X-Diverge-Wake-Splash: true header):

  1. Browser Detection: When all pods for the target preview environment are scaled to zero (0 ready pods) and the incoming request is a browser navigation (GET or HEAD with Accept: text/html), activator-proxy triggers the background scale-up and immediately returns 202 Accepted with Retry-After: 2.
  2. Auto-Refreshing Splash Screen: GET requests receive a lightweight HTML splash page (“Waking up preview environment…”) that polls GET /-/wakeup-status every 1.5 seconds and automatically reloads the page once the pod passes readiness probes, while HEAD requests receive only the status and headers with no body.
  3. Readiness Endpoint (GET /-/wakeup-status): Returns JSON {"ready": true|false} so both the splash screen and external health checkers can inspect cold-start progress without triggering additional upstream proxies.
  4. API & CLI Transparency: Non-HTML requests (POST, PUT, application/json, gRPC, or curl without Accept: text/html) bypass the splash screen and block until the pod is ready before forwarding the request.

Even without Knative or KEDA installed, Diverge’s EnvironmentReconciler supports native Idle Hibernation for standard Kubernetes deployments using two annotations on the Environment CR:

Annotation Example Description
divergedev.com/hibernate-after "30m" Duration of inactivity after which the controller scales managed preview Deployments and StatefulSets to 0 replicas and sets divergedev.com/hibernated: "true" (Hibernated condition).
divergedev.com/last-traffic-at "2026-10-12T01:15:00Z" RFC3339 timestamp of the last observed HTTP/gRPC request (updated by diverge-proxy / activator-proxy). If omitted, inactivity is measured from status.createdAt.

When traffic arrives again or divergedev.com/hibernated is cleared, the controller wakes the hibernated environment back to Running.

Namespace Quotas & FinOps LRU Auto-Hibernation

Section titled “Namespace Quotas & FinOps LRU Auto-Hibernation”

Platform teams can enforce hard capacity and FinOps budget guardrails per Kubernetes Namespace using annotations on the Namespace resource:

Namespace Annotation Example Description
divergedev.com/max-active-environments "10" Maximum concurrent non-hibernated preview Environments in the namespace
divergedev.com/max-cnpg-clusters "3" Maximum concurrent dedicated CloudNativePG (cnpg/fresh/snapshot) database clusters
divergedev.com/max-hourly-cost-usd "2.50" Maximum aggregate hourly FinOps burn rate across active Environments in the namespace
divergedev.com/quota-exceed-policy "hibernate-oldest" "hibernate-oldest" (default — scales the least-recently-used active Environments to 0 replicas to free budget) or "reject" (marks new environments Failed with QuotaReady=False / Reason: QuotaExceeded)

Here is what happens when a user accesses a scaled-to-zero preview environment:

sequenceDiagram
    actor Developer
    participant Proxy as Activator Proxy
    participant Knative as Knative / K8s Scale
    participant K8s as Kubernetes API
    participant Pod as Preview Pod
    
    Developer->>Proxy: GET / (Accept: text/html, Header: X-Preview-Env)
    Proxy->>K8s: Check Ready Pods
    K8s-->>Proxy: 0 Pods Ready
    
    Proxy->>Knative: Trigger Scale Deployment to 1
    Proxy-->>Developer: 202 Accepted (Wake-Up Splash HTML)
    
    K8s->>Pod: Start Container
    Developer->>Proxy: GET /-/wakeup-status?env=pr-42
    Pod-->>K8s: Pod Ready
    Proxy-->>Developer: {"ready": true, "readyPods": 1}
    
    Note over Developer, Pod: Browser Auto-Reloads & Subsequent Requests
    Developer->>Proxy: GET /
    Proxy->>K8s: Check Ready Pods
    K8s-->>Proxy: 1 Pod Ready
    Proxy->>Pod: Direct Forward (Bypass Cold-Start Path)
    Pod-->>Developer: 200 OK

Diverge supports three scale-to-zero and deployment mechanisms:

  • Annotation-based Idle Hibernation (divergedev.com/hibernate-after): Works with standard Kubernetes Deployments (--deploy-provider=direct) without extra CRDs.
  • KEDA HTTP Add-on (spec.serviceConfig.keda.enabled: true): Reconciles a KEDA HTTPScaledObject (and ScaledObject for Kafka/Temporal workers) per Environment.
  • Knative Serving (--deploy-provider=knative): Reconciles Knative Service resources that scale to zero natively.

Configure KEDA HTTP scale-to-zero and idle hibernation on an Environment via spec.serviceConfig.keda:

apiVersion: divergedev.com/v1alpha1
kind: Environment
metadata:
name: payments-pr-42
annotations:
divergedev.com/hibernate-after: "30m"
spec:
serviceConfig:
serviceName: payments-api
image: ghcr.io/acme/payments:pr-42
port: 8080
keda:
enabled: true
cooldownPeriod: 300
minReplicas: 0
maxReplicas: 5