GCP Cloud Run — Serverless Container Deployment Guide

Sanjeev SharmaSanjeev Sharma
5 min read

Advertisement

Introduction

Why This Matters

Google Cloud Run runs containers as serverless services — you push a container image and Google handles infrastructure, scaling from zero to thousands of instances, load balancing, and TLS. Unlike Lambda, Cloud Run works with any language and runtime because it runs standard containers. It is the fastest path from a Dockerfile to a production URL on GCP, with no cluster management required.

Deploying Services

# Authenticate and set project
gcloud auth login
gcloud config set project my-project-id
gcloud config set run/region us-central1
 
# Build and push container to Artifact Registry
gcloud artifacts repositories create my-repo \
  --repository-format=docker \
  --location=us-central1
 
gcloud builds submit --tag us-central1-docker.pkg.dev/my-project/my-repo/my-app:v1
 
# Deploy service
gcloud run deploy my-service \
  --image us-central1-docker.pkg.dev/my-project/my-repo/my-app:v1 \
  --platform managed \
  --region us-central1 \
  --allow-unauthenticated \
  --port 8080 \
  --memory 512Mi \
  --cpu 1 \
  --concurrency 80 \
  --min-instances 0 \
  --max-instances 100 \
  --set-env-vars NODE_ENV=production,LOG_LEVEL=info
 
# Get service URL
gcloud run services describe my-service \
  --region us-central1 \
  --format 'value(status.url)'

Environment Variables and Secrets

# Set environment variables
gcloud run deploy my-service \
  --image my-image \
  --set-env-vars KEY1=value1,KEY2=value2
 
# Update single env var without redeployment
gcloud run services update my-service \
  --update-env-vars DATABASE_URL=postgres://...
 
# Mount secrets from Secret Manager
gcloud run deploy my-service \
  --image my-image \
  --set-secrets DB_PASSWORD=myapp-db-password:latest,API_KEY=myapp-api-key:1
 
# Or mount as file
gcloud run deploy my-service \
  --image my-image \
  --set-secrets /secrets/config=myapp-config:latest

Traffic Splitting and Canary Deployments

# Deploy new revision without sending traffic
gcloud run deploy my-service \
  --image my-image:v2 \
  --no-traffic \
  --tag v2
 
# Send 10% traffic to new version (canary)
gcloud run services update-traffic my-service \
  --to-tags v2=10
 
# Gradually increase traffic
gcloud run services update-traffic my-service \
  --to-tags v2=50
 
# Promote to 100% when confident
gcloud run services update-traffic my-service \
  --to-latest
 
# Rollback — redirect all traffic to previous revision
gcloud run services update-traffic my-service \
  --to-revisions PREV_REVISION=100

VPC and Private Access

# Connect Cloud Run to VPC for private database access
gcloud run deploy my-service \
  --image my-image \
  --vpc-connector my-vpc-connector \
  --vpc-egress private-ranges-only
 
# Create serverless VPC Access connector
gcloud compute networks vpc-access connectors create my-connector \
  --region us-central1 \
  --range 10.8.0.0/28 \
  --network default
 
# For private-only service (no public internet)
gcloud run deploy my-service \
  --image my-image \
  --no-allow-unauthenticated \
  --ingress internal

Authentication and IAM

# Service-to-service authentication
# Caller service: get identity token
TOKEN=$(gcloud auth print-identity-token)
curl -H "Authorization: Bearer $TOKEN" https://my-service-url.run.app
 
# Grant specific service account permission to invoke
gcloud run services add-iam-policy-binding my-service \
  --member serviceAccount:caller@my-project.iam.gserviceaccount.com \
  --role roles/run.invoker
 
# Use Workload Identity for GKE-to-Cloud Run calls
# Or Cloud Tasks with OIDC token for async triggers

Cloud Run Jobs (Batch)

# Create a batch job (not an HTTP service)
gcloud run jobs create data-import \
  --image us-central1-docker.pkg.dev/my-project/my-repo/importer:latest \
  --region us-central1 \
  --memory 1Gi \
  --cpu 2 \
  --max-retries 3 \
  --task-timeout 3600s \
  --parallelism 5 \
  --tasks 100 \
  --set-env-vars BATCH_SIZE=1000
 
# Execute job
gcloud run jobs execute data-import
 
# Schedule with Cloud Scheduler
gcloud scheduler jobs create http nightly-import \
  --schedule "0 2 * * *" \
  --uri https://us-central1-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/my-project/jobs/data-import:run \
  --oauth-service-account-email scheduler@my-project.iam.gserviceaccount.com

CI/CD with Cloud Build

# cloudbuild.yaml
steps:
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$SHORT_SHA', '.']
 
  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$SHORT_SHA']
 
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    args:
      - gcloud
      - run
      - deploy
      - my-service
      - --image=us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$SHORT_SHA
      - --region=us-central1
      - --platform=managed
 
images:
  - 'us-central1-docker.pkg.dev/$PROJECT_ID/my-repo/my-app:$SHORT_SHA'

Common Mistakes

  • Using --allow-unauthenticated for internal services — services that only receive traffic from other services should use IAM authentication
  • Not setting --min-instances 1 for latency-sensitive services — cold starts can take 1-3 seconds for large images
  • Ignoring the 60-minute request timeout limit — Cloud Run is designed for HTTP; use Cloud Run Jobs for long-running batch work
  • Not configuring VPC connector for services that need private database access — Cloud Run uses public internet by default
  • Setting concurrency too high for CPU-intensive workloads — lower concurrency ensures each request gets adequate CPU

Best Practices

  • Use Artifact Registry (not Container Registry) for new projects — it supports multi-format repos and fine-grained IAM
  • Set --concurrency based on your application characteristics — I/O-bound apps handle more concurrent requests per instance
  • Use Cloud Run traffic splitting for canary deployments instead of big-bang releases
  • Enable Cloud Logging and Cloud Trace — they integrate automatically with Cloud Run at no configuration cost
  • Pin container image tags to digests in production (@sha256:...) rather than mutable tags like latest
  • Use service accounts with minimum permissions for each Cloud Run service

Key Takeaways

  • Cloud Run runs any containerized HTTP application — no cluster management, no infrastructure provisioning
  • Scale-to-zero means you pay nothing when the service receives no traffic — ideal for dev/staging environments
  • Cold starts occur when a new instance starts — --min-instances 1 eliminates cold starts at the cost of always-on compute
  • Traffic splitting allows canary deployments — route a percentage of traffic to a new revision before full rollout
  • Cloud Run Jobs handle batch workloads with configurable task count, parallelism, and retries
  • VPC connector is required for Cloud Run to reach private resources like Cloud SQL in a VPC
  • IAM controls invocation permissions — internal services should require authentication via service account tokens
  • --concurrency controls how many simultaneous requests one instance handles — tune based on CPU vs I/O workload type

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro

Related reading