Scaling & Cold Starts
How functions scale on demand, share instances, and pay for cold starts.
Functions scale automatically. Daytona starts managed microVM instances to serve invocations and scales them back to zero after a period of idleness — there is no autoscaler to configure and no minimum instance count to hold. Instance creation is fast enough that scaling is purely demand-driven: when invocations arrive, capacity appears; when they stop, it drains away. This page explains the two knobs that shape that behavior (max_concurrency and max_instances), how idle scale-down works, and what a cold start costs.
Demand-driven scaling
You never provision instances. When an invocation arrives with no warm instance to serve it, Daytona starts one; when instances sit idle past the function's scaledown_window_s, they are removed. Because you are billed per invocation duration, idle warm capacity and instance startup are Daytona's cost, not yours — you pay only for the wall-clock time your handler actually runs.
The scaledown_window_s field (default 60, minimum 2) sets how long an idle instance lingers before being scaled to zero. A longer window keeps instances warm between bursts (fewer cold starts, at no extra billing); a shorter window releases capacity sooner.
Concurrency per instance
max_concurrency (default 1) controls how many invocations a single instance serves at the same time. It's the main lever for how many instances a given load needs:
- Keep it at
1for CPU-bound handlers or handlers that aren't thread-safe — each invocation gets a dedicated instance. - Raise it for I/O-bound handlers (calling external APIs, waiting on databases), where one instance can interleave many concurrent requests. With
max_concurrency: 10, ten simultaneous invocations share one instance instead of starting ten.
Because max_concurrency is a runtime scaling knob, you can change it on the current version in place without redeploying:
Capping instances as a spend guard
max_instances optionally caps how many instances can run concurrently. It defaults to unbounded and is a spend guardrail, not a capacity or performance setting — set it to bound the worst-case cost of a traffic spike or a runaway fan-out. When the cap is hit, additional invocations queue, and they may return 429 (with Retry-After: 5) if they can't be placed within the queue budget. Send an explicit "max_instances": null to PATCH /fns/:name/config to remove the cap.
Cold starts vs. warm instances
The first invocation after a function has been idle pays a cold start: a fresh instance boots and the handler initializes before it can serve the request. Subsequent invocations reuse the warm instance and skip that cost. Each synchronous invoke tells you which it was through the x-rlp-cold response header (1 for cold, 0 for warm), and the invocation log records cold per invocation.
To reduce cold-start latency for handlers with heavy initialization, deploy with snapshot_kind: "fn_ready". This uses a cold-start template in which the handler is pre-warmed, so a new instance resumes ready to serve rather than initializing from scratch. It's purely a latency optimization: when a pre-warmed template isn't available, Daytona transparently falls back to the default pre_entrypoint behavior, so it's always safe to request.
Handling backpressure
When demand outruns available (or capped) capacity, an invocation waits in a bounded queue. If it can't be placed within the queue budget (about 30 seconds), the invoke returns 429 with Retry-After: 5 and the invocation is recorded as shed. Retry after the indicated delay; a warm instance or a freshly started one usually frees up quickly. Raising max_concurrency and, if you've set one, max_instances both reduce how often invocations shed under load.