Cloud Run — Serverless Containers
Cloud Run is GCP’s fully managed serverless container platform. Deploy any container image and Cloud Run handles scaling, HTTPS, load balancing, and infrastructure — you pay only when requests are being processed. Cloud Run powers Cloud Functions 2nd gen under the hood and is the recommended choice for most containerized workloads that do not need Kubernetes complexity.
Cloud Run vs. Alternatives
| Criteria | Cloud Run | Cloud Functions | GKE | Compute Engine |
|---|---|---|---|---|
| Deployment unit | Container image | Function code | Kubernetes manifests | VM image |
| Scaling | 0 to N (automatic) | 0 to N (automatic) | HPA + Cluster Autoscaler | MIG autoscaling |
| Max timeout | 60 minutes | 60 minutes (gen2) | Unlimited | Unlimited |
| Cold start | ~1-3 seconds | ~0.5-2 seconds | Pod schedule time | N/A (always on) |
| Networking | VPC connector or direct | VPC connector or direct | Full VPC | Full VPC |
| Best for | APIs, microservices | Event handlers | Complex orchestration | Legacy, full OS control |
Deploy a Service
# Build and push image to Artifact Registry
gcloud artifacts repositories create app \
--repository-format=docker --location=us-central1
gcloud builds submit --tag us-central1-docker.pkg.dev/learning-gcp-dev/app/web:v1 .
# Deploy to Cloud Run
gcloud run deploy web-app \
--image=us-central1-docker.pkg.dev/learning-gcp-dev/app/web:v1 \
--region=us-central1 \
--platform=managed \
--allow-unauthenticated \
--memory=512Mi \
--cpu=1 \
--min-instances=0 \
--max-instances=10 \
--concurrency=80 \
--port=8080 \
--set-env-vars=ENV=production \
--service-account=web-app@learning-gcp-dev.iam.gserviceaccount.com
Sample Dockerfile
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 8080
CMD ["node", "server.js"]
Cloud Run requires the container to listen on the port specified by the PORT environment variable (default 8080).
Configuration Options
gcloud run deploy web-app \
--image=us-central1-docker.pkg.dev/learning-gcp-dev/app/web:v2 \
--region=us-central1 \
--memory=1Gi \
--cpu=2 \
--cpu-boost \
--min-instances=1 \
--max-instances=100 \
--concurrency=100 \
--timeout=300 \
--set-secrets=DB_PASSWORD=db-password:latest,API_KEY=api-key:latest \
--vpc-connector=projects/learning-gcp-dev/locations/us-central1/connectors/my-connector \
--vpc-egress=private-ranges-only \
--no-cpu-throttling \
--session-affinity
| Setting | Purpose | Production Tip |
|---|---|---|
--min-instances=1 |
Eliminate cold starts | Use for latency-critical APIs |
--cpu-boost |
Extra CPU during startup | Reduces cold start time |
--concurrency |
Requests per instance | Higher = fewer instances, more memory per request |
--no-cpu-throttling |
CPU always allocated | Background processing in same container |
--session-affinity |
Sticky sessions | WebSocket or stateful sessions |
Cloud Run Jobs
For batch and scheduled workloads (not HTTP services):
gcloud run jobs create data-export \
--image=us-central1-docker.pkg.dev/learning-gcp-dev/app/exporter:v1 \
--region=us-central1 \
--memory=2Gi \
--cpu=2 \
--max-retries=3 \
--task-timeout=3600 \
--set-env-vars=EXPORT_BUCKET=learning-gcp-dev-exports
# Execute manually
gcloud run jobs execute data-export --region=us-central1
# Schedule with Cloud Scheduler
gcloud scheduler jobs create http nightly-export \
--schedule="0 3 * * *" \
--uri="https://us-central1-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/learning-gcp-dev/jobs/data-export:run" \
--http-method=POST \
--oauth-service-account-email=scheduler@learning-gcp-dev.iam.gserviceaccount.com
VPC and Private Connectivity
Access resources on private IPs (Cloud SQL, Memorystore, internal APIs):
# Create VPC connector
gcloud compute networks vpc-access connectors create my-connector \
--region=us-central1 \
--network=learning-vpc \
--range=10.8.0.0/28 \
--min-instances=2 \
--max-instances=10
# Deploy with VPC access
gcloud run deploy web-app \
--vpc-connector=my-connector \
--vpc-egress=private-ranges-only \
--region=us-central1
For Cloud SQL, prefer the Cloud SQL connector library over VPC connector when possible — lower latency and no connector cost.
Custom Domains and TLS
# Map custom domain
gcloud run domain-mappings create \
--service=web-app \
--domain=api.example.com \
--region=us-central1
# Verify DNS records shown in output
gcloud run domain-mappings describe \
--domain=api.example.com \
--region=us-central1
Cloud Run automatically provisions and renews TLS certificates for mapped domains.
Traffic Management
# Deploy new revision without routing traffic
gcloud run deploy web-app \
--image=us-central1-docker.pkg.dev/learning-gcp-dev/app/web:v3 \
--region=us-central1 \
--no-traffic
# Split traffic (canary deployment)
gcloud run services update-traffic web-app \
--region=us-central1 \
--to-revisions=web-app-v2=90,web-app-v3=10
# Rollback
gcloud run services update-traffic web-app \
--region=us-central1 \
--to-revisions=web-app-v2=100
Real-World Scenario: API Platform
A startup runs 8 microservices on Cloud Run:
Cloud Load Balancer (global)
├── api.example.com → Cloud Run: api-gateway
├── auth.example.com → Cloud Run: auth-service
└── app.example.com → Cloud Run: web-frontend
Internal (VPC):
├── Cloud Run: order-service → Cloud SQL (private IP)
├── Cloud Run: payment-service → external API via VPC
└── Cloud Run: notification-service → Pub/Sub
| Service | Min Instances | Max | Memory | Monthly Cost |
|---|---|---|---|---|
| api-gateway | 1 | 50 | 512Mi | ~$30 |
| auth-service | 1 | 20 | 256Mi | ~$15 |
| order-service | 0 | 100 | 1Gi | ~$20 (spiky) |
| batch-export (Job) | 0 | — | 2Gi | ~$5 |
Total: ~$70/month vs. ~$400/month for equivalent GKE cluster.
Common Mistakes
| Mistake | Impact | Fix |
|---|---|---|
| Container not listening on PORT | Deployment fails health check | Read $PORT env var |
| No min-instances on critical APIs | Cold start latency (1-3s) | Set --min-instances=1 |
| Large container images | Slow cold starts | Multi-stage Docker builds, distroless images |
| No max-instances limit | Runaway cost during attack | Set --max-instances |
| Stateful data in container | Lost on scale-down | Use Cloud SQL, Firestore, GCS |
Best Practices
- Use Artifact Registry with vulnerability scanning enabled
- Keep container images small (< 200MB) with multi-stage builds
- Set resource limits (memory, CPU) based on load testing
- Use Secret Manager for credentials, not environment variables in source
- Implement health check endpoints (
/health,/ready) - Use structured JSON logging for Cloud Logging integration
- Configure IAM — avoid
--allow-unauthenticatedunless truly public - Use traffic splitting for canary deployments
- Monitor with Cloud Monitoring request latency and error rate metrics
Troubleshooting
Container failed to start:
gcloud run services describe web-app --region=us-central1 \
--format="value(status.conditions)"
gcloud logging read 'resource.type="cloud_run_revision" severity>=ERROR' --limit=10
502 Bad Gateway:
Container is not listening on the correct port or health check fails. Verify PORT env var and startup time vs. timeout.
Cold start too slow:
gcloud run deploy web-app --cpu-boost --min-instances=1 --region=us-central1
# Reduce image size; use smaller base image (distroless, alpine)
Cannot reach Cloud SQL:
Use Cloud SQL connector library or verify VPC connector and --vpc-egress settings.
Permission denied on deploy:
gcloud projects add-iam-policy-binding learning-gcp-dev \
--member="user:[email protected]" \
--role="roles/run.developer"
Cloud Run is the sweet spot between serverless simplicity and container flexibility on GCP.
Next: Advanced Networking — hybrid connectivity and private service access.