Email copiado — support@tuurt.com
Cargando experiencia
gcp · October 05, 2026 · 7 min

Cold Starts in Cloud Run: When Minimum Instances Are Worth the Cost and When They Are Not

Cloud Run's cold start is not a universal problem: it matters when a user is waiting on the response and almost never when the work runs in the background. We look at what controls startup time and what happened when we set minimum instances on the wrong service.

By Tuurt Team

Cold Starts in Cloud Run: When Minimum Instances Are Worth the Cost and When They Are Not

Cloud Run spins container instances up and down based on demand. When there is no traffic, a revision can scale to zero: no instance keeps running, and the service costs nothing while nobody is using it. The problem shows up on the other side of that equation, the moment a request arrives and no instance is ready to handle it. At that point GCP has to bring up a new one: prepare the container, start the process inside it, and wait until that process is actually ready to respond. That interval is the cold start, and the mistake we keep seeing in new projects is treating it as a binary problem - "we have cold starts, so we need to get rid of them" - instead of first asking whether that startup time is actually being paid by a user waiting for a response, or by a background process that nobody cares takes a bit longer to come up.

What actually controls how long a revision takes to start

How long a new instance takes to become ready depends on a handful of factors that, for the most part, sit under the control of the team building the service, not under Cloud Run itself.

The first is the size and composition of the container image. An image built on a heavy base, with layers that include build tooling or dependencies that were only needed to produce the artifact and are never touched at runtime, takes longer to become ready than a multi-stage image whose final stage contains nothing but the runtime and the binaries strictly required to run.

The second is the language runtime and what that runtime needs to do before it can execute the first line of the application. A statically compiled Go binary starts the process almost immediately. A runtime like the JVM has to load classes, initialize the classloader, and let the JIT start compiling before reaching a stable performance state, which adds initialization work that a compiled binary simply does not have. Node and Python, for their part, pay the cost of interpreting and evaluating the startup code - including every top-level import and require - before the process can accept a connection.

The third factor, and the one that most often decides whether a startup is fast or slow, is whatever the application's own code does before marking itself ready: opening a database connection pool, loading a model into memory, reading secrets from Secret Manager one at a time instead of in a single call, or running migrations on boot instead of as a separate deployment step. Cloud Run also offers a CPU boost mode during startup, designed specifically to shorten that initial phase for runtimes with heavier initialization work, but that boost helps with available CPU time, not with external dependencies that are slow to respond on their own.

When the cold start genuinely matters

It matters when there is a human waiting for that response on the other end, synchronously, inside a window of time where an extra startup is noticeable: an endpoint serving a mobile app or a web interface, an API called directly from the browser before a screen can render, any flow where the user is staring at a spinner while they wait.

It also matters when the service's traffic pattern has gaps: usage spikes separated by quiet periods long enough that Cloud Run decides to scale to zero between one spike and the next. In that pattern, every spike pays the full startup cost again on its first requests, because no instance survived from the previous spike to absorb them.

When it does not matter, even though it technically exists

It does not matter for work triggered by a Pub/Sub message or a Cloud Tasks job that a worker processes asynchronously, with no user staring at a spinner in the meantime: there, the startup time is simply folded into a total processing time that nobody is measuring against an expectation of an immediate response anyway.

It does not matter either for a job triggered by Cloud Scheduler that runs once an hour or once a day: the startup is a small fraction of the batch job's total duration, and optimizing it does not change, in any practical sense, when the job actually finishes.

And it does not matter, even though it technically still exists, for a service with sustained traffic where the configured concurrency and the real request volume keep instances alive almost all the time. There, a cold start happens at most once after each deployment, and every request after that lands on an instance that is already warm.

What fixing it with minimum instances actually costs

The direct way to eliminate the cold start is to configure a minimum number of instances that Cloud Run keeps running at all times, even with no traffic. That works: an instance that is already up does not need to start. But that reserved capacity is billed continuously, whether or not a request actually shows up to use it at any given moment. Setting minimum instances with no justification beyond "starting fast is better" turns an occasional, one-off cost - the startup of a new instance when one is actually needed - into a fixed, permanent cost, for a benefit that is only noticeable in the first few milliseconds of the requests that would otherwise have hit a service scaled to zero.

What we fixed: minimum instances set on the wrong service

In a project with several Cloud Run services sitting behind the same API, one of them served a mobile application directly, and keeping it at a minimum instance count made real sense, since its responses are part of a flow where the user is actively waiting. Weeks later, the team started getting reports that the app felt slow at certain times of day, and someone, looking for a quick fix, also raised the minimum instance count on an internal service that processes events from a Pub/Sub subscription, assuming more instances would always help against the reported slowness.

That change did not touch the real problem. The internal service never had anyone waiting on its response synchronously: it processed its messages within a time that was already acceptable for the asynchronous flow it was part of, whether or not instances were pre-warmed. The slowness users were reporting was eventually traced to an external dependency that the mobile-facing endpoint called synchronously and that, at certain times, took longer than usual to respond - something no minimum instance count on any Cloud Run service was ever going to fix. The outcome of that change was paying for reserved capacity on a service where nobody was waiting on that capacity in real time, while the problem actually affecting users went untouched until the slow dependency was identified and addressed directly, with bounded retries and a stricter timeout on the mobile-facing endpoint.

What we check before deciding a cold start is worth acting on

Before touching a service's configuration over a reported or suspected cold start, we first confirm that someone is genuinely waiting for that response synchronously, inside a window of time where an extra startup would actually be noticed. We check the service's real traffic pattern, because a service with sustained traffic almost never scales to zero, which makes the cold start a non-issue in practice even though it technically still exists. We treat reported slowness and cold starts as separate possible causes, because conflating the two leads to spending budget on minimum instances without touching the actual problem, which is exactly what happened in the case above. And before reserving capacity permanently, we check whether the startup itself can be shortened at the source - trimming the image, deferring dependencies that are not needed for the first request, or batching secret reads into a single call - because that fix addresses the slow startup directly, instead of paying, month after month, to hide it behind instances that never turn off.

gcp cloud-run serverless devops
← Back to blog