Pavan Rangani

HomeBlogGCP Cloud Run: Serverless Containers with Automatic Scaling to Zero

GCP Cloud Run: Serverless Containers with Automatic Scaling to Zero

By Pavan Rangani · April 7, 2026 · Cloud Management

GCP Cloud Run: Serverless Containers with Automatic Scaling to Zero

GCP Cloud Run: The Simplest Way to Run Containers

GCP Cloud Run serverless is Google Cloud’s fully managed container platform that automatically scales from zero to thousands of instances based on incoming traffic. You bring a container image, Cloud Run handles everything else — provisioning, scaling, TLS certificates, and load balancing. Therefore, teams can deploy any application that fits in a container without managing any infrastructure. In practice, the appeal is that the same image you run locally with docker run is the artifact you ship to production, with no platform-specific packaging in between.

Cloud Run’s killer feature is scale-to-zero — when your service receives no traffic, it runs zero instances and you pay nothing. Moreover, it scales up in seconds when requests arrive, handling traffic spikes without pre-provisioning capacity. Consequently, Cloud Run is ideal for APIs, web applications, webhooks, and event-driven workloads where traffic is variable or unpredictable. Because billing is per 100ms of request time and rounded by allocated CPU and memory, a service that sits idle overnight costs literally nothing while it waits.

GCP Cloud Run Serverless: Deploying Your First Service

Deploy a container to Cloud Run with a single command. Cloud Run pulls your image, configures networking, provisions TLS, and makes your service available at a unique HTTPS URL. Furthermore, you can deploy from source code directly — Cloud Run uses Cloud Build with buildpacks to containerize your application automatically, which is handy for prototypes but worth replacing with an explicit Dockerfile once you care about image size and reproducibility.

# Deploy from a container image
gcloud run deploy order-service \
  --image gcr.io/my-project/order-service:v1.2.0 \
  --platform managed \
  --region us-central1 \
  --memory 1Gi \
  --cpu 2 \
  --min-instances 0 \
  --max-instances 100 \
  --concurrency 80 \
  --port 8080 \
  --set-env-vars "SPRING_PROFILES_ACTIVE=production" \
  --set-secrets "DB_PASSWORD=db-password:latest" \
  --vpc-connector my-vpc-connector \
  --allow-unauthenticated

# Deploy from source (Cloud Build auto-builds)
gcloud run deploy my-api \
  --source . \
  --region us-central1

# Deploy with traffic splitting (canary)
gcloud run services update-traffic order-service \
  --to-revisions LATEST=10,order-service-v1=90 \
  --region us-central1
GCP Cloud Run serverless deployment
Cloud Run automatically scales from zero to handle any traffic pattern

Service Configuration and Scaling

Cloud Run provides fine-grained control over scaling behavior, request handling, and resource allocation. The concurrency setting determines how many requests each instance handles simultaneously — higher concurrency means fewer instances but more memory per instance. Additionally, minimum instances keep your service warm for latency-sensitive endpoints. Choosing concurrency well is the highest-leverage tuning decision you will make: a CPU-bound service often performs best at a concurrency of 1 to 10, while an I/O-bound API that mostly waits on a database can happily serve 80 or more requests per instance.

# Cloud Run service YAML configuration
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: order-service
  annotations:
    run.googleapis.com/launch-stage: GA
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/minScale: "1"
        autoscaling.knative.dev/maxScale: "100"
        run.googleapis.com/cpu-throttling: "false"
        run.googleapis.com/startup-cpu-boost: "true"
        run.googleapis.com/vpc-access-connector: my-vpc-connector
        run.googleapis.com/vpc-access-egress: private-ranges-only
    spec:
      containerConcurrency: 80
      timeoutSeconds: 300
      containers:
        - image: gcr.io/my-project/order-service:v1.2.0
          ports:
            - containerPort: 8080
          resources:
            limits:
              cpu: "2"
              memory: 1Gi
          env:
            - name: SPRING_PROFILES_ACTIVE
              value: production
          startupProbe:
            httpGet:
              path: /actuator/health
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
            failureThreshold: 12
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080

Cold Starts and the CPU Allocation Model

The trade-off behind scale-to-zero is the cold start: when a request arrives and no instance is running, Cloud Run must pull the image, start the container, and wait for it to pass its startup probe before routing traffic. For a slim Go or Node service this can be well under a second, but a JVM application loading a Spring context can take several seconds, which is why startup-cpu-boost exists — it temporarily grants extra CPU during startup to shorten that window.

There is also a subtle billing nuance in how CPU is allocated. By default Cloud Run throttles CPU to near zero between requests, so any background work — async logging, a scheduled flush, a connection keep-alive — may stall while an instance is idle. Setting cpu-throttling: "false" keeps the CPU allocated for the instance’s full lifetime, which is what you want for services doing background processing, at the cost of paying for that CPU continuously. Therefore, choose request-based CPU for stateless request/response APIs and always-allocated CPU only when a workload genuinely needs to compute between requests.

VPC Connectivity and Private Services

Cloud Run services can connect to resources in your VPC — databases, Redis caches, internal APIs — through VPC connectors or Direct VPC egress. Furthermore, you can restrict ingress to internal traffic only, making services accessible only from within your VPC or through a load balancer. A common production layout puts Cloud Run behind a Google Cloud HTTPS Load Balancer with Cloud Armor in front, so that public clients hit the load balancer while the service itself accepts traffic only from internal sources.

Cloud networking and VPC architecture
VPC connectors enable Cloud Run services to access private resources securely

Securing Service-to-Service Calls

When one Cloud Run service calls another, do not reach for --allow-unauthenticated. Instead, give each service a dedicated runtime service account, grant the caller the roles/run.invoker role on the callee, and have the caller attach a Google-signed identity token to each request. This keeps internal traffic authenticated by IAM rather than by network position, which is far safer than relying on the VPC perimeter alone.

# Service-to-service call with an OIDC identity token
import urllib.request
import google.auth.transport.requests
import google.oauth2.id_token

TARGET = "https://inventory-service-abc123-uc.a.run.app"

def fetch_inventory(sku: str) -> bytes:
    auth_req = google.auth.transport.requests.Request()
    # Token audience MUST match the target service URL
    token = google.oauth2.id_token.fetch_id_token(auth_req, TARGET)

    req = urllib.request.Request(
        f"{TARGET}/inventory/{sku}",
        headers={"Authorization": f"Bearer {token}"},
    )
    with urllib.request.urlopen(req) as resp:
        return resp.read()

CI/CD with Cloud Build

Automate deployments with Cloud Build triggers that build, test, and deploy on every push. Additionally, use traffic splitting for gradual rollouts and automatic rollback on failure. See the Cloud Run documentation for advanced deployment patterns. The pattern below deploys a new revision with no traffic, tags it as canary, and then shifts a small percentage to it — letting you watch error rates before promoting to 100 percent.

Key Takeaways

  • Start with a solid foundation and build incrementally based on your requirements
  • Test thoroughly in staging before deploying to production environments
  • Monitor performance metrics and iterate based on real-world data
  • Follow security best practices and keep dependencies up to date
  • Document architectural decisions for future team members
# cloudbuild.yaml — CI/CD pipeline
steps:
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', 'gcr.io/$PROJECT_ID/order-service:$SHORT_SHA', '.']

  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'gcr.io/$PROJECT_ID/order-service:$SHORT_SHA']

  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: gcloud
    args:
      - 'run'
      - 'deploy'
      - 'order-service'
      - '--image=gcr.io/$PROJECT_ID/order-service:$SHORT_SHA'
      - '--region=us-central1'
      - '--tag=canary'
      - '--no-traffic'

  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: gcloud
    args:
      - 'run'
      - 'services'
      - 'update-traffic'
      - 'order-service'
      - '--to-tags=canary=10'
      - '--region=us-central1'

images:
  - 'gcr.io/$PROJECT_ID/order-service:$SHORT_SHA'
CI/CD deployment pipeline
Cloud Build automates container builds and canary deployments to Cloud Run

When NOT to Reach for Cloud Run

Cloud Run is excellent for stateless HTTP and event-driven workloads, but it is not the answer to every problem. Long-running stateful processes, anything that needs to hold a stable in-memory session across requests, or workloads that depend on a persistent local disk fit poorly into a model where instances can be created and destroyed at will. Likewise, a request that legitimately runs longer than the configured timeout — heavy batch jobs, large media transcodes — belongs on Cloud Run jobs, GKE, or Batch rather than the request-serving runtime.

There are cost edges too. A service that never idles and runs at steady high concurrency may be cheaper on GKE Autopilot or committed-use instances, because scale-to-zero buys you nothing when you never scale to zero. And teams that need fine-grained control over networking, sidecars, or custom schedulers will eventually feel the platform’s deliberate simplicity as a constraint. For deeper comparisons, see serverless vs containers cost and performance and the broader GKE Autopilot managed Kubernetes guide. Used for what it is good at, though, Cloud Run remains the fastest path from code to a running, autoscaled, TLS-terminated endpoint on Google Cloud.

In conclusion, GCP Cloud Run serverless is the fastest path from container to production on Google Cloud. With scale-to-zero pricing, automatic TLS, and built-in traffic management, it removes operational overhead while giving you full container flexibility. Start with a simple deployment, add VPC connectivity for database access, secure service-to-service calls with IAM, and implement canary deployments for safe releases.

← Back to all articles