Skip to main content

Pinning apps warm with c2a app autoscale

By default, Knative scales your app to zero pods when idle. The first request after an idle period pays a cold-start cost (~1–10s depending on image size + startup). For latency-sensitive apps or webhooks that can't tolerate cold starts, you want to keep at least one pod warm.

c2a app autoscale lets you set this without kubectl or a redeploy.

Common recipes

# Pin warm — always keep ≥1 pod running
c2a app autoscale my-app --min 1

# Allow scale-to-zero (the default for new apps; useful to revert a pin)
c2a app autoscale my-app --scale-to-zero

# Cap upper bound — prevent runaway scaling under load
c2a app autoscale my-app --min 1 --max 5

# Full config — pin warm, cap at 5, hold scaling decisions over 30 min
c2a app autoscale my-app --min 1 --max 5 --window 30m

# Dry-run — see the exact API call without executing
c2a app autoscale my-app --min 1 --dry-run

Flags

FlagWhat
--min NMinimum replicas. 0 = scale-to-zero allowed. 1+ = pin warm.
--max NMaximum replicas (≥ 1).
--windowKnative stable window for autoscaling decisions (e.g. 30m, 60s). Default upstream: 60s. Longer windows ride out traffic spikes; shorter ones react faster.
--scale-to-zeroEquivalent to --min 0. Convenience flag.
--dry-runPrint the API call without executing. Works without auth.

Pass only the flags you want to change — the others are left untouched.

Why this survives c2a app rebuild

Old advice for pinning warm was to kubectl patch the ksvc directly. That works once, but the next c2a app rebuild (or any other ksvc reconciliation) could overwrite it.

c2a app autoscale uses a focused server-side merge-patch that writes only the autoscaling.knative.dev/* annotations. Other annotations (e.g. those from Daari Secrets rotation or build-metadata tracking) and other ksvc lanes (env, envFrom, bindings, labels) survive untouched. Subsequent rebuilds only mutate the kpack Image, not the ksvc spec — so the autoscale setting persists.

Cost implications

Pinning warm costs more — the pod runs continuously instead of just on demand. Concretely:

  • --min 1 on a --size small app: ≈ $4–$8/month idle (driven by your platform CCU rate; see /costs)
  • --min 0 (scale-to-zero): you only pay for requests handled

For internal tools, dev environments, and async workers, scale-to-zero is usually correct. For user-facing APIs and webhooks (e.g. Stripe, GitHub, MCP servers), pin warm.

When the new setting takes effect

Setting an annotation on spec.template.metadata causes Knative to roll a fresh revision. The new minScale takes effect on the next pod schedule (seconds, not minutes). You can verify with:

c2a app describe my-app
# or
kubectl get ksvc -n <project-ns> my-app -o jsonpath='{.spec.template.metadata.annotations}'

Troubleshooting

  • "Unauthorized" / 401 — run c2a login first.
  • Setting doesn't seem to apply — check the latest ready revision: kubectl get revision -n <ns> -l serving.knative.dev/service=<app> --sort-by=.metadata.creationTimestamp. If Knative hasn't created a new revision, look at the ksvc events: kubectl describe ksvc <app> -n <ns>.
  • Pod is up but still cold on first request — your image probably has a slow startup (heavy framework boot, ML model load). Reduce startup time at the image level; pinning won't fully eliminate cold-start because Knative may still spin up additional pods under load.

Where this fits in the shared responsibility model

This is in your lane (developer / app owner) — you decide whether your app needs to stay warm. The platform exposes the verb; you pick the value. See the Shared Responsibility Model for the broader split.