Skip to main content

Autoscaling and scale to zero

This page explains how the platform decides how many copies of your service to run, and what happens when that number is zero. You control three settings, and you will meet all three below. One example service, checkout-api, runs through the whole page.

The three knobs you control

Every service has a scaling block with three settings:

{"scaling": {"min_scale": 0, "max_scale": 10, "container_concurrency": 80}}
  • min_scale (default 0) — the floor: the fewest instances kept running, even with no traffic. 0 means the service is allowed to scale to zero.
  • max_scale (default 10) — the ceiling: the platform will never run more instances than this, no matter the load.
  • container_concurrency (default 80) — how many requests one instance handles at the same time before the platform adds another instance.

An instance is one running copy of your container. The math is simple. Say 200 requests arrive at once and each instance handles 80. The platform runs 3 instances, because 200 ÷ 80 rounds up to 3. It does that only if 3 sits between your floor and your ceiling.

In the console, these appear in the deploy dialog's Advanced options as Minimum instances, Maximum instances, and Concurrent requests per instance.

max_scale: 0 does not mean unlimited

Today the platform treats max_scale: 0 (or a negative value) as the default of 10 when it configures the autoscaler. Do not set 0 expecting unbounded scaling — you will get a ceiling of 10. If you need a higher ceiling, set the number you actually want.

container_concurrency: 0 is different. It means "use the serverless default", which in practice puts no ceiling on requests per instance. Negative values for any of the three are rejected with a 400. So is a min_scale greater than max_scale.

The life of an idle service

checkout-api is deployed with the defaults (0 / 10 / 80). Here is its day:

  1. Traffic arrives. One instance starts and serves requests. Load rises to about 160 requests at a time, so a second instance is added automatically.
  2. Traffic stops. After roughly 90 seconds of no requests, instances drain to zero. That 90 seconds is two waits added together: the platform first watches for a stretch of steady no-traffic, then waits out a short grace period on top. The service now costs nothing in compute.
  3. A request arrives at 3 a.m. This one pays a cold start, the delay you get when no instance is running yet. It waits a few seconds while the platform brings up a fresh instance, then it is served normally. Requests right behind it are fast, because the instance is now warm.

You can watch this live. Every interface reads the same metrics endpoint, which reports the current instance count:

platformctl serverless metrics checkout-api

You should see:

running instances 0
state ready
ready yes
since 2026-08-12T01:58:11Z
scaling 0..10 instances, 80 concurrent requests each

REVISION INSTANCES READY TRAFFIC LATEST
checkout-api-00003 0 yes 100% yes

Trimmed here: the real output ends with the endpoint's own note about what it cannot show, which is the paragraph below in the platform's own words.

running instances 0 and ready yes together are not a contradiction. state and ready are the same two fields the service itself carries — over the API they read "state": "ready" and "ready": true — and ready means what it means everywhere on this platform: can I use this right now, not is something running. A service at rest has scaled to zero and is still perfectly able to answer.

Service metrics provide real-time readouts of live request rates, instance counts, and resource utilization.

Asking for logs while the service sits at zero is not an error either. You get a 200 with an explanation:

platformctl serverless logs checkout-api

You should see:

no running instances: this service is scaled to zero. Send it a request and the logs will appear here.
Testing scale-to-zero

Do not write a script that sleeps a fixed 90 seconds and then checks. The drain time is approximate, not exact. Instead, check the instance count over and over until it reaches zero. Give up only after a generous timeout of about 3 minutes. This matters most in an automated build pipeline, where a fixed sleep turns into a flaky test.

Keep one instance warm

Sometimes a multi-second cold start is unacceptable. Say checkout-api sits in a checkout flow a customer is watching. Set the floor to one:

platformctl serverless update checkout-api --min-scale 1

You should see the service's row, showing that the change was accepted:

NAME STATE PUBLISHED URL
checkout-api ready no http://<private-hostname>

Scaling is the one flag group on serverless update that merges. Naming --min-scale alone leaves --max-scale and --concurrency exactly as they were. Every other group — --env, the resource flags, --publish — replaces whole.

The trade is explicit. min_scale: 1 means no cold starts. It also means one instance running around the clock, drawing on your project's CPU and memory quota the whole time. That is the whole deal. There is no separate "provisioned concurrency" product or pricing tier to learn.

A PATCH merges the scaling block

The curl tab above sends all three scaling fields even though only min_scale changed. That is a good habit, because it states your full intent, and it is what the console does. It is not required, though. A scaling block in a PATCH is merged onto the service's current scaling, which is exactly why the CLI can send --min-scale on its own. A sub-field you leave out keeps its current value; it does not fall back to the platform default.

Request timeouts

Each request gets up to timeout_seconds to finish. The default is 300, and you can set anything from 1 to 3600. The platform injects that value into your container as the environment variable CRUSOE_REQUEST_TIMEOUT_SECONDS, so your code can read its own deadline instead of hard-coding one.

platformctl serverless update checkout-api --timeout 600

Summary

SettingDefaultRange / ruleWhat it does
min_scale0≥ 0; must be ≤ max_scaleFloor. 0 allows scale to zero; 1 keeps a warm instance and eliminates cold starts
max_scale10≥ 0; 0 currently behaves as 10Ceiling on instances under load
container_concurrency80≥ 0; 0 = platform default (effectively unbounded)Simultaneous requests per instance before another is added
timeout_seconds3001–3600Per-request deadline; mirrored into CRUSOE_REQUEST_TIMEOUT_SECONDS
Idle-to-zero time~90 sHow long a service with min_scale: 0 sits idle before draining to zero
Cold starta few secondsDelay on the first request after idle

Next steps