Cleaning Up Idle Cloud Run Revisions That Still Bill
9 min read · updated August 11, 2026
Cloud Run scales to zero, which is most of why people choose it. So a bill that keeps growing while traffic is flat reads as impossible. There is one specific configuration that produces it, and once you can name it the cleanup is mechanical.
Why a revision with no traffic still runs
A Cloud Run revision receiving no traffic scales to zero instances by default and costs nothing. Minimum instances exist to defeat that default deliberately: they keep an instance warm so the next request does not pay a cold start. Google documents that instances kept running this way do incur charges — under request-based billing at a reduced idle rate while they are not processing requests, and under instance-based billing at the full rate for the entire instance lifecycle.
On its own that is expected and priced-in. The surprise comes from the interaction with traffic tags. A tag makes a revision individually addressable at its own URL — the mechanism behind preview URLs and blue-green rollouts. Google’s documentation states it plainly: with revision-level minimum instances and traffic tags configured, all tagged revisions are started and kept active even when there are no incoming requests. The minimum instances documentation is the reference.
So the shape of the problem is: a team adopts blue-green deploys, every deploy tags a revision, nobody removes old tags, and every one of those revisions holds a warm instance forever. The bill grows one instance per deploy, at zero traffic, and the service dashboard shows a healthy service serving 100% from the current revision.
Two different minimum-instance settings
Cloud Run has two minimum-instance settings with confusingly similar names, and which one you used decides whether you have this problem at all.
- Service-level. The gcloud flag is
--min, and the YAML annotation isrun.googleapis.com/minScale. It applies across the revisions that are receiving traffic, distributed proportionally to the traffic split, and it takes effect without deploying a new revision. Google recommends this one. - Revision-level. The gcloud flag is
--min-instances, and the annotation isautoscaling.knative.dev/minScale. It applies to one specific revision and takes effect when that revision is deployed — and it is the setting that keeps tagged revisions warm.
The flags differ by a suffix. That is genuinely the whole difference at the command line, and it is why this configuration is usually reached by accident rather than by decision. Google’s own remedy is direct: to avoid incurring charges for tagged revisions, use service-level minimum instances, or remove the tags when you no longer need them.
Finding the ones that bill
Three facts to gather per service: which revisions exist, which hold a revision-level minimum, and which carry traffic or a tag.
# every revision, with its revision-scoped minScale
gcloud run revisions list \
--service my-service --region europe-west1 \
--format="table(
metadata.name,
metadata.annotations['autoscaling.knative.dev/minScale']:label=MIN,
status.conditions[0].lastTransitionTime:label=UPDATED)"
# the traffic block, which is where the tags live
gcloud run services describe my-service \
--region europe-west1 \
--format="yaml(spec.traffic)"A revision that appears in the second output with a tag and in the first with a MIN greater than zero, and which has no non-zero percent, is one that is costing money and serving nothing. That is the list.
To confirm from the billing side rather than the config side, the Cloud Run SKUs in the BigQuery billing export distinguish idle from active CPU allocation. A non-trivial idle line against a service whose traffic is bursty is the same finding arrived at from the other direction, and it is the one to put in front of somebody who does not believe the config reading.
Removing them safely
- Confirm the revision is at 0% traffic. Google documents that a revision at zero traffic can be deleted even if it has an assigned tag, and that Cloud Run then removes the tag from the service’s traffic configuration automatically.
- Prefer removing the tag first if the revision itself is still wanted as a rollback target:
gcloud run services update-traffic my-service --remove-tags=preview-42. That stops the billing without discarding the artefact. - Delete revisions you genuinely do not need:
gcloud run revisions delete REVISION --region REGION. Keep the two or three most recent as rollback targets; deleting all history trades a small saving for a real operational risk. - Re-run the describe command and confirm the traffic block no longer mentions the tag.
- Watch the idle-allocation line in the billing export over the next few days. The config change is immediate; the billing evidence is not.
One thing not to do: deleting the revision that is currently serving 100% of traffic. Cloud Run will refuse, but scripts that iterate over revisions by age and delete anything older than N days have a habit of being run against a service that has not deployed in a while.
Preventing the recurrence
Cleanup that is not paired with a change to the deploy path is cleanup you will do again next quarter. Three changes remove the class of problem rather than the instance of it.
Move minimum instances to the service level. If the reason for a warm instance is user-facing latency, the requirement belongs to the service, not to a particular revision, and the service-level setting expresses that exactly. It also survives deployment, so nobody has to remember to set it again.
Make tags expire. If preview URLs are part of the review flow, make removing the tag part of merging the pull request — the same automation that closes the branch. A tag with no owner and no expiry is a warm instance with no owner and no expiry.
And add the revision list to whatever periodic review already exists. The check is one command and its output is short; the cost of skipping it compounds one instance per deploy.