A comprehensive Terraform module for deploying serverless applications on GCP using Cloud Run with scale-to-zero capability, API Gateway, and optional state management with Memorystore Redis or Firestore.
This module provides a complete serverless deployment solution on GCP:
- β‘ Cloud Run: Serverless containers with scale-to-zero (min_instance_count=0) β no idle cost, cold starts on first request
- π API Gateway: OpenAPI-based routing with versioned endpoints and JWT authorization support
- π Flexible Deployment: Automatic Docker image build and push to Artifact Registry
- π VPC Integration: VPC Connector for private networking with Redis and Firestore
- πΎ State Management: Optional Memorystore Redis or Firestore for session/state persistence
- π Cloud Logging: Built-in logging and monitoring (no extra configuration needed)
- ποΈ Automated Infrastructure: VPC, subnets, NAT Gateway, and firewall rules created automatically
Perfect for microservices, API backends, event-driven architectures, and serverless web applications requiring REST endpoints with enterprise API management.
| Name | Version |
|---|---|
| Terraform | >= 1.9.5 |
| Google Provider | >= 6.8.0 |
| Google Beta Provider | >= 6.8.0 |
| Docker Provider | 3.6.2 |
module "serverless_agent" {
source = "yaalalabs/ak-serverless/google"
project_id = "my-gcp-project"
region = "us-central1"
product_alias = "myapp"
env_alias = "prod"
product_display_name = "My Application API"
module_name = "api"
package_path = "${path.module}/dist"
cpu = "1"
memory = "512Mi"
timeout = 30
environment_variables = {
ENVIRONMENT = "production"
LOG_LEVEL = "info"
}
# API Gateway
api_version = "v1"
api_base_path = "api"
agent_endpoint = "chat"
tags = {
environment = "production"
service = "api"
}
}
output "api_url" {
value = module.serverless_agent.agent_invoke_url
}
output "service_url" {
value = module.serverless_agent.service_url
}module "serverless_api_redis" {
source = "yaalalabs/ak-serverless/google"
project_id = "my-gcp-project"
region = "us-central1"
product_alias = "myapp"
env_alias = "prod"
product_display_name = "Serverless API with Redis"
module_name = "chat"
package_path = "${path.module}/dist"
# Enable Memorystore Redis for session storage
create_redis_cluster = true
cpu = "1"
memory = "512Mi"
environment_variables = {
ENVIRONMENT = "production"
# Redis URL automatically injected as AK_SESSION__REDIS__URL
}
api_version = "v1"
agent_endpoint = "chat"
}module "serverless_api_firestore" {
source = "yaalalabs/ak-serverless/google"
project_id = "my-gcp-project"
region = "us-central1"
product_alias = "myapp"
env_alias = "prod"
product_display_name = "Serverless API with Firestore"
module_name = "chat"
package_path = "${path.module}/dist"
# Enable Firestore for session storage (GCP equivalent of DynamoDB)
create_firestore_database = true
environment_variables = {
ENVIRONMENT = "production"
# Firestore connection details automatically injected:
# AK_SESSION__FIRESTORE__DATABASE
# AK_SESSION__FIRESTORE__PROJECT
}
api_version = "v1"
agent_endpoint = "chat"
}module "serverless_api_vpc" {
source = "yaalalabs/ak-serverless/google"
project_id = "my-gcp-project"
region = "us-central1"
product_alias = "myapp"
env_alias = "prod"
product_display_name = "Serverless API with Existing VPC"
module_name = "api"
package_path = "${path.module}/dist"
# Use existing VPC instead of creating new one
network_id = "projects/my-project/global/networks/my-vpc"
private_subnet_id = "projects/my-project/regions/us-central1/subnetworks/private-subnet"
# Specify connector CIDR that doesn't overlap with existing subnets
connector_cidr = "10.8.1.0/28"
create_redis_cluster = true
api_version = "v1"
agent_endpoint = "chat"
}module "production_api" {
source = "yaalalabs/ak-serverless/google"
project_id = "enterprise-gcp-project"
region = "us-central1"
product_alias = "enterprise"
env_alias = "prod"
product_display_name = "Enterprise Production API"
module_name = "core-api"
package_path = "${path.module}/dist"
is_production = true
# Keep instances warm to eliminate cold starts
min_instance_count = 2
max_instance_count = 50
# High performance configuration
cpu = "4"
memory = "8Gi"
timeout = 300
backend_deadline = 300
# Enable both Redis and Firestore
create_redis_cluster = true
create_firestore_database = true
# JWT Authorization
authorizer = {
issuer = "https://accounts.google.com"
jwks_uri = "https://www.googleapis.com/oauth2/v3/certs"
audiences = ["your-client-id.apps.googleusercontent.com"]
}
# Restrict direct Cloud Run access
allow_unauthenticated_invocation = false
# API throttling
throttling_rate_limit = 100 # requests per second
throttling_burst_limit = 200 # burst capacity
environment_variables = {
ENVIRONMENT = "production"
LOG_LEVEL = "warn"
ENABLE_METRICS = "true"
}
api_version = "v1"
agent_endpoint = "enterprise"
tags = {
environment = "production"
compliance = "SOC2"
cost_center = "engineering"
criticality = "high"
}
}module "custom_endpoints_api" {
source = "yaalalabs/ak-serverless/google"
project_id = "my-gcp-project"
region = "us-central1"
product_alias = "myapp"
env_alias = "dev"
module_name = "api"
package_path = "${path.module}/dist"
api_version = "v1"
api_base_path = "api"
agent_endpoint = "chat"
# Define custom API endpoints
gateway_endpoints = [
{
path = "health"
method = "GET"
overwrite_path = "/health"
},
{
path = "custom"
method = "POST"
overwrite_path = "/api/custom"
},
{
path = "status"
method = "GET"
overwrite_path = "/status"
}
]
# Results in API endpoints:
# GET /api/v1/health -> /health
# POST /api/v1/custom -> /api/custom
# GET /api/v1/status -> /status
# POST /api/v1/chat -> /api/v1/chat (default)
# POST /api/v1/chat-multipart -> /api/v1/chat-multipart (default)
}module "mcp_api" {
source = "yaalalabs/ak-serverless/google"
project_id = "my-gcp-project"
region = "us-central1"
product_alias = "myapp"
env_alias = "prod"
module_name = "mcp"
package_path = "${path.module}/dist"
# Enable MCP server endpoint
enable_mcp_server = true
api_version = "v1"
agent_endpoint = "chat"
# This creates an additional endpoint:
# ANY /api/v1/mcp -> /mcp/
}| Name | Description | Type | Default | Required |
|---|---|---|---|---|
project_id |
GCP project ID | string |
n/a | yes |
region |
GCP region for deployment | string |
n/a | yes |
product_alias |
Short identifier for the product | string |
n/a | yes |
env_alias |
Environment identifier (dev, staging, prod) | string |
n/a | yes |
product_display_name |
Human-readable product name | string |
"An Agent Kernel deployment" |
no |
module_name |
Module name for resource identification | string |
n/a | yes |
is_production |
Enable production features | bool |
false |
no |
package_path |
Path to Docker build context (dist/ directory with Dockerfile) | string |
n/a | yes |
environment_variables |
Environment variables for the container | map(string) |
{} |
no |
tags |
Resource labels for GCP resources | map(string) |
{} |
no |
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
cpu |
CPU allocation (e.g. '1' = 1 vCPU, '2' = 2 vCPUs, '4' = 4 vCPUs) | string |
"1" |
no |
memory |
Memory allocation (e.g. '512Mi', '1Gi', '2Gi', '8Gi') | string |
"512Mi" |
no |
timeout |
Maximum request timeout in seconds (max 3600) | number |
30 |
no |
min_instance_count |
Minimum instances (0 = scale to zero / serverless) | number |
0 |
no |
max_instance_count |
Maximum number of instances | number |
10 |
no |
container_port |
Port the container listens on | number |
8000 |
no |
health_check_endpoint |
Health check path | string |
"/health" |
no |
Key Feature: min_instance_count = 0 enables true serverless behavior (scale-to-zero). Set to 1+ to keep instances warm and eliminate cold starts.
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
api_version |
API version for endpoint path (e.g. 'v1', 'v2') | string |
"v1" |
no |
agent_endpoint |
Default API endpoint name | string |
"chat" |
no |
api_base_path |
Base path segment for API | string |
"api" |
no |
backend_deadline |
API Gateway backend deadline in seconds (must be <= timeout) | number |
30 |
no |
gateway_endpoints |
Additional API endpoints to expose | list(object) |
[] |
no |
enable_mcp_server |
Enable MCP server and expose /mcp API endpoint | bool |
false |
no |
Gateway Endpoints Object Structure:
gateway_endpoints = [
{
path = "health" # Path segment (without leading /)
method = "GET" # HTTP method: GET, POST, PUT, DELETE, PATCH, ANY
overwrite_path = "/health" # Target path on Cloud Run service
}
]| Name | Description | Type | Default | Required |
|---|---|---|---|---|
network_id |
VPC network ID. If null, a new VPC is created | string |
null |
no |
private_subnet_id |
Private subnet ID (when using existing VPC) | string |
null |
no |
connector_cidr |
CIDR block for VPC Access Connector. If null, auto-computed (e.g. '10.8.1.0/28') | string |
null |
no |
public_subnet_cidr |
CIDR for public subnet (when creating new VPC) | string |
"10.0.1.0/24" |
no |
private_subnet_cidr |
CIDR for private subnet (when creating new VPC) | string |
"10.0.2.0/24" |
no |
VPC Connector: Required for Cloud Run to access private resources (Redis, Firestore). The connector CIDR must not overlap with existing subnets.
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
create_redis_cluster |
Create Memorystore Redis instance for agent memory | bool |
false |
no |
create_firestore_database |
Create Firestore database for agent session storage | bool |
false |
no |
Redis Environment Variables (Auto-injected when enabled):
AK_SESSION__REDIS__URL: Complete Redis connection URL with authentication
Firestore Environment Variables (Auto-injected when enabled):
AK_SESSION__FIRESTORE__DATABASE: Firestore database IDAK_SESSION__FIRESTORE__PROJECT: GCP project ID
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
enable_cors |
Enable CORS pre-flight OPTIONS handling in API Gateway | bool |
true |
no |
cors_allow_origins |
CORS allowed origins (enforce in Cloud Run app) | list(string) |
["*"] |
no |
cors_allow_methods |
CORS allowed methods | list(string) |
["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] |
no |
cors_allow_headers |
CORS allowed headers | list(string) |
["*"] |
no |
cors_expose_headers |
CORS exposed headers | list(string) |
[] |
no |
cors_max_age |
CORS pre-flight cache max age in seconds | number |
600 |
no |
cors_allow_credentials |
Whether to allow credentials in CORS requests | bool |
false |
no |
Note: GCP API Gateway does not have native CORS handling like AWS API Gateway v2. When enable_cors = true, OPTIONS pre-flight handlers are added to the OpenAPI spec. The actual CORS response headers (Access-Control-Allow-*) must be set by your Cloud Run application (via FastAPI middleware or similar).
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
allow_unauthenticated_invocation |
Allow unauthenticated direct invocation of Cloud Run service URL | bool |
true |
no |
authorizer |
JWT authorizer configuration for API Gateway | object |
null |
no |
Authorizer Object Structure:
authorizer = {
issuer = "https://accounts.google.com" # JWT issuer URL
jwks_uri = "https://www.googleapis.com/oauth2/v3/certs" # JWKS endpoint
audiences = ["your-client-id.apps.googleusercontent.com"] # Expected JWT audiences
}When authorizer is set, API Gateway validates JWT tokens in every request before forwarding to Cloud Run.
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
throttling_rate_limit |
Steady-state request rate limit per second. Set null to disable | number |
null |
no |
throttling_burst_limit |
Burst request limit (token bucket size). Set null to disable | number |
null |
no |
Note: Both values must be provided to enable throttling. Set both to null to disable.
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
log_retention_days |
Log retention in days for Cloud Logging. If set, updates project default log bucket retention | number |
null |
no |
| Name | Description | Example |
|---|---|---|
service_url |
Direct Cloud Run service URL | https://myapp-prod-api-svc-abc123.run.app |
service_name |
Cloud Run service name | myapp-prod-api-svc |
service_account_email |
Service account email used by Cloud Run | myapp-prod-api-fn@project.iam.gserviceaccount.com |
gateway_url |
API Gateway base URL | myapp-prod-api-12345678-gw-abc123.uc.gateway.dev |
api_gateway_id |
API Gateway ID | myapp-prod-api-12345678-api |
agent_invoke_url |
Full agent invocation URL via API Gateway | https://myapp-prod-api-12345678-gw-abc123.uc.gateway.dev/api/v1/chat |
authorizer_status |
JWT authorizer configuration status | "JWT Authorizer configured (issuer: ...)" or "No authorizer configured β endpoints are publicly accessible" |
True Scale-to-Zero:
- min_instance_count = 0: No idle cost β pay only when requests are processed
- Cold Starts: First request after idle period experiences startup latency (~1-3 seconds)
- Always-On Option: Set
min_instance_count >= 1to keep instances warm and eliminate cold starts
Automatic Scaling:
- Scales from 0 to
max_instance_countbased on request load - Each instance handles multiple concurrent requests
- Automatic scale-down when traffic decreases
Resource Configuration:
- CPU: 1, 2, 4, 6, or 8 vCPUs
- Memory: 128Mi to 32Gi (must match CPU limits)
- Timeout: Up to 3600 seconds (1 hour)
Container Platform:
- Automatic Docker image build from source directory
- Push to Artifact Registry
- Deploy to Cloud Run with zero-downtime updates
OpenAPI-Based Routing:
https://{gateway-id}.{region}.gateway.dev/{api_base_path}/{version}/{endpoint}
Example: https://myapp-prod-api-12345678-gw-abc123.uc.gateway.dev/api/v1/chat
Features:
- OpenAPI 2.0 (Swagger) specification
- ESPv2 (Extensible Service Proxy) for routing
- Path rewriting to Cloud Run service
- Configurable backend deadline
- JWT authorization support
- Request throttling with quota management
Default Endpoints:
POST /{api_base_path}/{version}/{agent_endpoint}β/api/v1/chatPOST /{api_base_path}/{version}/{agent_endpoint}-multipartβ/api/v1/chat-multipart
Custom Endpoints:
- Define additional endpoints via
gateway_endpoints - Support for GET, POST, PUT, DELETE, PATCH, ANY methods
- Path rewriting for flexible routing
VPC Connector Architecture:
- Serverless VPC Access Connector enables Cloud Run to access private resources
- Connector CIDR: Auto-computed or manually specified (must be /28)
- Firewall rules automatically created for connector traffic
Private Resource Access:
- Memorystore Redis via private IP
- Firestore via private endpoint
- Other VPC resources via private networking
Security Features:
- Service Account with least-privilege IAM roles
- Optional JWT authorization at API Gateway
- Configurable Cloud Run invocation permissions
- VPC firewall rules for network isolation
When create_redis_cluster = true:
- Memorystore for Redis (managed Redis service)
- Basic tier (1GB) for development
- Standard tier (5GB+) for production with HA
- Private IP connectivity via VPC Connector
- Connection URL automatically injected as
AK_SESSION__REDIS__URL
When create_firestore_database = true:
- Firestore in Native mode (GCP equivalent of DynamoDB)
- Document-based NoSQL database
- Automatic scaling and high availability
- Connection details automatically injected:
AK_SESSION__FIRESTORE__DATABASEAK_SESSION__FIRESTORE__PROJECT
Cloud Run Serverless with Scale-to-Zero:
βββββββββββββββββββββββ
β Internet β
ββββββββββββ¬βββββββββββ
β HTTPS
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β API Gateway (ESPv2) β
β OpenAPI 2.0 Specification β
β β
β β’ JWT Authorization (optional) β
β β’ Request Throttling (optional) β
β β’ Path Rewriting β
β β’ CORS Pre-flight Handling β
βββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββ
β Backend Deadline
β Routes to Cloud Run
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Cloud Run Service β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Serverless Container Instances β β
β β β’ min_instance_count = 0 (scale to zero) β β
β β β’ max_instance_count = 10 β β
β β β’ CPU: 1 vCPU, Memory: 512Mi β β
β β β’ Timeout: 30s β β
β β β’ Service Account Identity β β
β β β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β Docker Container β β β
β β β β’ Built from package_path β β β
β β β β’ Pushed to Artifact Registry β β β
β β β β’ Listens on port 8000 β β β
β β β β’ Health check: /health β β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β β VPC Connector β
β βΌ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ
β VPC Network β
β β β
β ββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββ β
β β VPC Access Connector (10.8.0.0/28) β β
β β β’ Enables Cloud Run β VPC connectivity β β
β β β’ Auto-computed CIDR to avoid conflicts β β
β β β’ Firewall rules for connector traffic β β
β ββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββ β
β β Private Subnet (10.0.2.0/24) β β
β β β β β
β β ββββββββββββββ΄βββββββββββββ β β
β β β β β β
β β βΌ βΌ β β
β β ββββββββββββββββββββ ββββββββββββββββββββ β β
β β β Memorystore β β Firestore β β β
β β β Redis β β Database β β β
β β β (Optional) β β (Optional) β β β
β β ββββββββββββββββββββ ββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Public Subnet (10.0.1.0/24) β β
β β β β
β β ββββββββββββββββββββββββββ β β
β β β Cloud NAT β β β
β β β (Outbound Internet) β β β
β β ββββββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Traffic Flow:
1. Internet β API Gateway
2. API Gateway β Cloud Run (JWT validation, throttling)
3. Cloud Run β VPC Connector
4. VPC Connector β Redis/Firestore (private IP)
5. Response flows back through API Gateway
Key Architecture Points:
- Scale-to-Zero:
min_instance_count = 0means no instances run when idle (no cost) - Cold Starts: First request after idle triggers container startup (~1-3 seconds)
- VPC Connector: Required for Cloud Run to access private resources
- Automatic CIDR: Connector CIDR auto-computed to avoid conflicts
- Service Account: Dedicated identity with least-privilege IAM roles
# Light API calls (simple responses)
cpu = "1"
memory = "512Mi"
timeout = 10
# Data processing (moderate compute)
cpu = "2"
memory = "2Gi"
timeout = 60
# Heavy compute (ML inference, large data)
cpu = "4"
memory = "8Gi"
timeout = 300CPU-Memory Pairing:
- 1 vCPU: 128Mi - 4Gi
- 2 vCPU: 512Mi - 8Gi
- 4 vCPU: 2Gi - 16Gi
- 8 vCPU: 4Gi - 32Gi
# Use multi-stage builds
FROM python:3.12-slim as builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY . .
CMD ["python", "app.py"]Tips:
- Use slim base images
- Minimize layers
- Exclude unnecessary files (.dockerignore)
- Cache dependencies
locals {
config = {
dev = {
cpu = "1"
memory = "512Mi"
min_instance_count = 0 # Scale to zero
max_instance_count = 5
create_redis = false
create_firestore = true
}
prod = {
cpu = "4"
memory = "8Gi"
min_instance_count = 2 # Always warm
max_instance_count = 50
create_redis = true
create_firestore = true
}
}
env_config = local.config[var.env_alias]
}
module "api" {
source = "yaalalabs/ak-serverless/google"
# ... other config
cpu = local.env_config.cpu
memory = local.env_config.memory
min_instance_count = local.env_config.min_instance_count
max_instance_count = local.env_config.max_instance_count
create_redis_cluster = local.env_config.create_redis
create_firestore_database = local.env_config.create_firestore
}# Python FastAPI example
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
def health_check():
return {"status": "healthy", "service": "api"}
@app.get("/api/v1/chat")
def chat_endpoint():
# Your chat logic
return {"response": "Hello"}β
Set min_instance_count >= 1 to eliminate cold starts
β
Configure JWT authorization for API Gateway
β
Enable request throttling for rate limiting
β
Set allow_unauthenticated_invocation = false for stricter security
β
Use appropriate CPU and memory for workload
β
Configure Cloud Logging retention
β
Enable both Redis and Firestore for redundancy
β
Use existing VPC for network isolation
β
Set proper IAM roles and permissions
β
Test with realistic load
# Enable CORS in Terraform
module "api" {
# ... other config
enable_cors = true
cors_allow_origins = ["https://myapp.com", "https://app.myapp.com"]
cors_allow_methods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
cors_allow_headers = ["Content-Type", "Authorization"]
}# Enforce CORS in FastAPI application
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["https://myapp.com", "https://app.myapp.com"],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["Content-Type", "Authorization"],
expose_headers=["X-Request-ID"],
max_age=600,
)Note: GCP API Gateway handles OPTIONS pre-flight requests, but your Cloud Run application must set the actual CORS response headers.
Cloud Run Pricing:
CPU: $0.00002400 per vCPU-second
Memory: $0.00000250 per GiB-second
Requests: $0.40 per million requests
Example: 1M requests/month, 200ms avg execution, 512Mi memory, 0.5 vCPU:
- CPU cost: 1,000,000 Γ 0.2s Γ 0.5 vCPU Γ $0.000024 = $2.40
- Memory cost: 1,000,000 Γ 0.2s Γ 0.5 GiB Γ $0.0000025 = $0.25
- Request cost: 1,000,000 Γ $0.0000004 = $0.40
- Total: ~$3.05/month
Key Advantage: With min_instance_count = 0, you pay $0 when idle!
Additional Costs:
- VPC Connector: ~$7/month per instance (min 2 for HA) = ~$14/month
- Cloud NAT: ~$1/month + $0.045/GB processed
- Memorystore Redis (Basic, 1GB): ~$35/month
- Memorystore Redis (Standard, 5GB with HA): ~$175/month
- Firestore: Pay per read/write/delete operation
- Document reads: $0.06 per 100,000
- Document writes: $0.18 per 100,000
- Document deletes: $0.02 per 100,000
- API Gateway: $3 per million calls
- Artifact Registry: $0.10/GB storage per month
- Cloud Logging: First 50 GB/month free, then $0.50/GB
| Configuration | Cloud Run | VPC Connector | Redis | Firestore | API Gateway | Total/Month |
|---|---|---|---|---|---|---|
| Dev (scale-to-zero, Firestore) | $3 | $14 | $0 | ~$5 | $3 | ~$25 |
| Staging (1 warm instance, Redis) | $50 | $14 | $35 | $0 | $10 | ~$109 |
| Production (2+ warm, Redis HA) | $200 | $14 | $175 | $10 | $30 | ~$429 |
-
Use Scale-to-Zero for Development
min_instance_count = 0 # No idle cost
-
Right-Size Resources
- Monitor Cloud Run metrics to find optimal CPU/memory
- Reduce timeout to minimum needed
- Use smaller memory allocations when possible
-
Choose State Management Wisely
- Firestore: Better for variable workloads (pay per operation)
- Redis: Better for high-frequency access (fixed cost)
- Use Redis Basic tier for development
-
Optimize VPC Connector Usage
- Share one VPC Connector across multiple Cloud Run services
- Use existing VPC when possible
-
Clean Up Unused Resources
# List old container images gcloud artifacts docker images list \ ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY} # Delete old images gcloud artifacts docker images delete \ ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${IMAGE}:${TAG}
-
Monitor and Optimize
- Use Cloud Monitoring to track request patterns
- Adjust
max_instance_countbased on actual load - Enable request throttling to prevent cost spikes
# Calculate estimated monthly cost
REQUESTS_PER_MONTH=1000000
AVG_DURATION_MS=200
VCPU=1
MEMORY_GIB=0.5
CPU_COST=$(echo "$REQUESTS_PER_MONTH * ($AVG_DURATION_MS / 1000) * $VCPU * 0.000024" | bc -l)
MEMORY_COST=$(echo "$REQUESTS_PER_MONTH * ($AVG_DURATION_MS / 1000) * $MEMORY_GIB * 0.0000025" | bc -l)
REQUEST_COST=$(echo "$REQUESTS_PER_MONTH * 0.0000004" | bc -l)
TOTAL=$(echo "$CPU_COST + $MEMORY_COST + $REQUEST_COST" | bc -l)
echo "Estimated Cloud Run cost: \$$(printf '%.2f' $TOTAL)/month"Issue: Service fails to deploy or crashes on startup
Solutions:
-
Check Cloud Logging for errors:
gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=${SERVICE_NAME}" \ --limit 50 --format json
-
Verify Dockerfile builds locally:
cd ${PACKAGE_PATH} docker build -t test-image . docker run -p 8000:8000 test-image
-
Check health check endpoint returns 200:
curl http://localhost:8000/health
-
Verify environment variables are set correctly:
gcloud run services describe ${SERVICE_NAME} \ --region ${REGION} \ --format="value(spec.template.spec.containers[0].env)"
Issue: API Gateway returns "Not Found" for valid endpoints
Solutions:
-
Verify OpenAPI spec was deployed:
gcloud api-gateway api-configs describe ${CONFIG_ID} \ --api=${API_ID} \ --format=json
-
Check gateway_endpoints configuration matches your application routes
-
Ensure paths in OpenAPI spec match Cloud Run service paths
-
Test Cloud Run service directly (bypass API Gateway):
curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" \ ${SERVICE_URL}/api/v1/chat
Issue: Cloud Run service cannot reach Redis or Firestore
Solutions:
-
Verify VPC Connector is active:
gcloud compute networks vpc-access connectors describe ${CONNECTOR_NAME} \ --region ${REGION}
-
Check firewall rules allow traffic from connector:
gcloud compute firewall-rules list \ --filter="name~${FIREWALL_NAME}" -
Verify environment variables are injected:
# For Redis gcloud run services describe ${SERVICE_NAME} \ --region ${REGION} \ --format="value(spec.template.spec.containers[0].env[?(@.name=='AK_SESSION__REDIS__URL')].value)" # For Firestore gcloud run services describe ${SERVICE_NAME} \ --region ${REGION} \ --format="value(spec.template.spec.containers[0].env[?(@.name=='AK_SESSION__FIRESTORE__DATABASE')].value)"
-
Test connectivity from Cloud Run:
# Add to your application for debugging import socket redis_host = "10.0.2.3" # Your Redis private IP try: socket.create_connection((redis_host, 6379), timeout=5) print("Redis connection successful") except Exception as e: print(f"Redis connection failed: {e}")
Issue: First request after idle period is slow
Solutions:
-
Keep instances warm (eliminates cold starts):
min_instance_count = 1 # or higher
-
Optimize Docker image:
- Use smaller base images (alpine, slim)
- Minimize layers
- Cache dependencies
-
Reduce startup time:
- Lazy-load heavy dependencies
- Use startup CPU boost (automatic in Cloud Run)
- Optimize application initialization
-
Monitor cold start metrics:
gcloud logging read "resource.type=cloud_run_revision \ AND jsonPayload.message=~'Cold start'" \ --limit 10
Issue: Service account lacks required permissions
Solutions:
-
Check Service Account IAM roles:
gcloud projects get-iam-policy ${PROJECT_ID} \ --flatten="bindings[].members" \ --filter="bindings.members:serviceAccount:${SERVICE_ACCOUNT_EMAIL}"
-
Grant required roles:
# For Redis access gcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \ --role="roles/redis.editor" # For Firestore access gcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \ --role="roles/datastore.user"
-
Verify Cloud Run invocation permissions:
gcloud run services get-iam-policy ${SERVICE_NAME} \ --region ${REGION}
Issue: Connector CIDR overlaps with existing subnets
Solutions:
-
Manually specify non-overlapping CIDR:
connector_cidr = "10.8.1.0/28" # Must be /28
-
List existing subnet CIDRs:
gcloud compute networks subnets list \ --network=${NETWORK_NAME} \ --format="table(name,ipCidrRange)"
-
Choose CIDR from available ranges:
- 10.8.0.0/28 through 10.8.255.240/28 (256 possible /28 subnets)
- Must not overlap with any existing subnet
Issue: Valid JWT tokens are rejected
Solutions:
-
Verify authorizer configuration:
authorizer = { issuer = "https://accounts.google.com" # Must match JWT iss claim jwks_uri = "https://www.googleapis.com/oauth2/v3/certs" audiences = ["your-client-id.apps.googleusercontent.com"] # Must match JWT aud claim }
-
Test JWT token:
# Decode JWT to verify claims echo ${JWT_TOKEN} | cut -d'.' -f2 | base64 -d | jq
-
Check API Gateway logs:
gcloud logging read "resource.type=api AND resource.labels.service=${GATEWAY_ID}" \ --limit 20
Automatic Logging:
- Cloud Run automatically sends logs to Cloud Logging
- No additional configuration required
- Logs include request/response, errors, and custom application logs
View Logs:
# View recent logs
gcloud logging read "resource.type=cloud_run_revision \
AND resource.labels.service_name=${SERVICE_NAME}" \
--limit 50 --format json
# Follow logs in real-time
gcloud logging tail "resource.type=cloud_run_revision \
AND resource.labels.service_name=${SERVICE_NAME}"
# Filter by severity
gcloud logging read "resource.type=cloud_run_revision \
AND resource.labels.service_name=${SERVICE_NAME} \
AND severity>=ERROR" \
--limit 20Cloud Run Metrics:
- Request Count: Number of requests per second
- Request Latency: P50, P95, P99 latencies
- Instance Count: Active instances (should be 0 when idle if min=0)
- CPU Utilization: Percentage of allocated CPU used
- Memory Utilization: Percentage of allocated memory used
- Container Startup Latency: Cold start duration
API Gateway Metrics:
- Request Count: Requests through API Gateway
- Error Rate: 4xx and 5xx responses
- Latency: End-to-end request latency
- Quota Usage: Throttling quota consumption (if enabled)
State Management Metrics:
- Redis: Connection count, memory usage, operations/sec
- Firestore: Read/write operations, document count
# Cloud Run request count
gcloud monitoring time-series list \
--filter='metric.type="run.googleapis.com/request_count" AND resource.labels.service_name="${SERVICE_NAME}"' \
--format=json
# Cloud Run latencies
gcloud monitoring time-series list \
--filter='metric.type="run.googleapis.com/request_latencies" AND resource.labels.service_name="${SERVICE_NAME}"' \
--format=json
# API Gateway request count
gcloud monitoring time-series list \
--filter='metric.type="serviceruntime.googleapis.com/api/request_count" AND resource.labels.service="${GATEWAY_ID}"' \
--format=json# Python example using OpenTelemetry
from opentelemetry import metrics
from opentelemetry.exporter.cloud_monitoring import CloudMonitoringMetricsExporter
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
# Configure Cloud Monitoring exporter
exporter = CloudMonitoringMetricsExporter()
reader = PeriodicExportingMetricReader(exporter)
provider = MeterProvider(metric_readers=[reader])
metrics.set_meter_provider(provider)
# Create custom metrics
meter = metrics.get_meter(__name__)
request_counter = meter.create_counter(
"custom.requests",
description="Number of requests processed",
)
# Increment counter
request_counter.add(1, {"endpoint": "/chat", "status": "success"})Create Alert Policy:
# Alert on high error rate
gcloud alpha monitoring policies create \
--notification-channels=${CHANNEL_ID} \
--display-name="High Error Rate" \
--condition-display-name="Error rate > 5%" \
--condition-threshold-value=0.05 \
--condition-threshold-duration=300s \
--condition-filter='resource.type="cloud_run_revision" AND metric.type="run.googleapis.com/request_count" AND metric.labels.response_code_class="5xx"'
# Alert on high latency
gcloud alpha monitoring policies create \
--notification-channels=${CHANNEL_ID} \
--display-name="High Latency" \
--condition-display-name="P95 latency > 1s" \
--condition-threshold-value=1000 \
--condition-threshold-duration=300s \
--condition-filter='resource.type="cloud_run_revision" AND metric.type="run.googleapis.com/request_latencies" AND metric.labels.percentile="95"'Enable Cloud Trace:
# Python example
from opentelemetry import trace
from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
# Configure Cloud Trace exporter
trace.set_tracer_provider(TracerProvider())
tracer = trace.get_tracer(__name__)
span_exporter = CloudTraceSpanExporter()
span_processor = BatchSpanProcessor(span_exporter)
trace.get_tracer_provider().add_span_processor(span_processor)
# Create spans
with tracer.start_as_current_span("process_request"):
# Your code here
with tracer.start_as_current_span("database_query"):
# Database operation
pass# Create log-based metric for custom events
gcloud logging metrics create custom_event_count \
--description="Count of custom events" \
--log-filter='resource.type="cloud_run_revision" AND jsonPayload.event="custom_event"'
# Query log-based metric
gcloud monitoring time-series list \
--filter='metric.type="logging.googleapis.com/user/custom_event_count"' \
--format=json| Feature | AWS | Azure | GCP |
|---|---|---|---|
| Serverless Compute | Lambda | Azure Functions | Cloud Run (min_instance=0) |
| Container Platform | ECS Fargate | Container Apps | Cloud Run (min_instanceβ₯1) |
| API Gateway | API Gateway | API Management | API Gateway (ESPv2) |
| Session Storage (NoSQL) | DynamoDB | Cosmos DB | Firestore |
| Session Storage (Cache) | ElastiCache Redis | Azure Cache for Redis | Memorystore for Redis |
| Container Registry | ECR | ACR | Artifact Registry |
| VPC | VPC | VNet | VPC Network |
| Private Connectivity | Security Groups | NSG | VPC Connector + Firewall Rules |
| Identity | IAM Role | Managed Identity | Service Account |
| Logging | CloudWatch Logs | Application Insights | Cloud Logging |
| Monitoring | CloudWatch Metrics | Azure Monitor | Cloud Monitoring |
| Tracing | X-Ray | Application Insights | Cloud Trace |
| Cloud | Service | Scale-to-Zero | Cold Start | Always-On Option |
|---|---|---|---|---|
| AWS | Lambda | β Always | Yes (~1-3s) | β (use Fargate) |
| Azure | Functions | β Consumption plan | Yes (~1-3s) | β Premium plan |
| GCP | Cloud Run | β min_instance=0 | Yes (~1-3s) | β min_instanceβ₯1 |
GCP Advantage: Single service (Cloud Run) supports both serverless and always-on modes via min_instance_count parameter.
| Feature | AWS API Gateway | Azure APIM | GCP API Gateway |
|---|---|---|---|
| Specification | OpenAPI 3.0 | OpenAPI 3.0 | OpenAPI 2.0 (Swagger) |
| Native CORS | β Yes | β Yes | β No (app must handle) |
| JWT Authorization | Lambda Authorizer | JWT validation | β Native JWT validation |
| Throttling | β Native | β Native | β Quota-based |
| WebSocket | β Yes | β No | β No |
| Request Transformation | β VTL templates | β Policies | β Limited |
| Cloud | Compute | API Gateway | Total |
|---|---|---|---|
| AWS Lambda | ~$3.50 | ~$3.50 | ~$7.00 |
| Azure Functions | ~$6.60 | ~$3.50 | ~$10.10 |
| GCP Cloud Run | ~$3.05 | ~$3.00 | ~$6.05 |
Note: GCP is most cost-effective for serverless workloads, especially with scale-to-zero.
| Feature | AWS | Azure | GCP |
|---|---|---|---|
| NoSQL Database | DynamoDB | Cosmos DB | Firestore |
| Pricing Model | Pay per request | Pay per RU | Pay per operation |
| Free Tier | 25 GB, 25 RCU/WCU | 1000 RU/s, 25 GB | 1 GB storage, 50K reads/day |
| Cache Service | ElastiCache Redis | Azure Cache for Redis | Memorystore for Redis |
| Cache Pricing | ~$15/month (1GB) | ~$16/month (1GB) | ~$35/month (1GB) |
Similarities:
- Both support scale-to-zero
- Both use container images
- Both integrate with API Gateway
Differences:
- Packaging: Lambda uses ZIP or container; Cloud Run always uses containers
- Timeout: Lambda max 15 minutes; Cloud Run max 60 minutes
- Memory: Lambda 128MB-10GB; Cloud Run 128Mi-32Gi
- Cold Starts: Similar (~1-3 seconds)
Migration Steps:
- Containerize Lambda function (if using ZIP)
- Update environment variable names
- Replace DynamoDB with Firestore or ElastiCache with Memorystore
- Update API Gateway configuration (OpenAPI 2.0 vs 3.0)
- Update IAM roles to Service Account
Similarities:
- Both support scale-to-zero and always-on
- Both use container-based deployment
- Both integrate with API Management/Gateway
Differences:
- Configuration: Azure uses host.json; GCP uses Terraform variables
- Bindings: Azure has input/output bindings; GCP uses standard HTTP
- Networking: Azure uses VNet integration; GCP uses VPC Connector
Migration Steps:
- Convert Azure Functions to standard HTTP handlers
- Remove Azure-specific bindings
- Replace Cosmos DB with Firestore or Azure Cache with Memorystore
- Update APIM policies to API Gateway OpenAPI spec
- Update Managed Identity to Service Account
- Cloud Run Documentation - Official Cloud Run documentation
- API Gateway Documentation - API Gateway setup and configuration
- Memorystore for Redis - Managed Redis service
- Firestore Documentation - NoSQL document database
- VPC Documentation - Virtual Private Cloud networking
- Serverless VPC Access - VPC Connector setup
- Cloud Logging - Logging and log analysis
- Cloud Monitoring - Metrics and monitoring
- Cloud Run Pricing - Detailed pricing information
- API Gateway Pricing - API Gateway costs
- Memorystore Pricing - Redis pricing tiers
- Firestore Pricing - NoSQL database costs
- GCP Pricing Calculator - Estimate total costs
- Cloud Run Best Practices - Performance and optimization
- Container Image Optimization - Reduce cold starts
- Security Best Practices - Secure your services
- Troubleshooting Guide - Common issues and solutions
- Google Provider Documentation - Terraform GCP provider
- Cloud Run Resource - Cloud Run Terraform resource
- API Gateway Resources - API Gateway Terraform resources
- GCP Community - Community forums and discussions
- Stack Overflow - Q&A for Cloud Run
- GitHub Issues - Report issues with GCP SDKs
- GCP Common Module - Shared infrastructure modules for GCP deployments
- GCP Containerized Module - Always-on Cloud Run deployment (min_instanceβ₯1)
- Artifact Registry Module - Container image management
- VPC Module - Virtual Private Cloud networking
- Memorystore Module - Managed Redis clusters
- Firestore Module - NoSQL database setup
- AWS Serverless Module - AWS Lambda deployment
- Azure Serverless Module - Azure Functions deployment
- GCP Serverless OpenAI Example - Multi-agent OpenAI deployment with Redis
- GCP Serverless Firestore Example - OpenAI deployment with Firestore
- GCP Containerized Example - Always-on deployment with custom routes
Note: This module automatically creates VPC networks, subnets, VPC Connectors, firewall rules, and IAM bindings. Ensure your GCP credentials have sufficient permissions to create these resources. The Service Account used by Cloud Run is granted the minimum required IAM roles for accessing Redis and Firestore.
Unless otherwise specified, all content, including all source code files and documentation files in this repository are:
Copyright (c) 2025-2026 Yaala Labs.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.