Effortless Production Deployment with Google Cloud Run
Interactive developer workshop: achieving frictionless, zero-ops production deployments for Go and React monorepos on Google Cloud Run in Jakarta (asia-southeast2).
Session Roadmap
How Google Cloud Run eliminates the operational friction of traditional Kubernetes and VM deployments across five core phases.
Core principles of serverless containers on GCP.
- GCP Compute Spectrum
- Scale-to-zero & cold starts
- Concurrency multiplexing
- Artifact Registry & IAM
Monorepo services and containers.
- Go 1.24 Distroless API
- Vite React Nginx SPA
- Dynamic PORT & 0.0.0.0
- Graceful SIGTERM draining
Step-by-step terminal execution.
- gcloud auth & project setup
- Deploy API & extract URL
- Deploy Web with BACKEND_URL
- One-click deploy.sh script
Automation, metrics & logging.
- Cloud Build 5-step CI/CD
- Terraform resource models
- 4 Golden Signals & Metrics
- Structured logs & Cloud Trace
Interactive discussion & recap.
- Audience Q&A
- Cost crossover trade-offs
- Troubleshooting live bugs
- Resource links & code repo
Where Cloud Run Fits in Google Cloud
Comparing compute abstractions to select the optimal model for containerized web services.
Direct VM instances with full kernel & OS control.
- Manual patching & OS updates
- Always-on billing when idle
- Scaling takes minutes per VM
- Best for legacy, stateful apps
Managed Kubernetes for complex multi-cluster microservices.
- Full control over K8s APIs
- Persistent disks & stateful sets
- High cluster maintenance overhead
- Pay for node pools & management
Fully managed environment for standard stateless containers.
- Zero cluster ops: deploy any container
- Scale-to-zero when idle ($0 cost)
- Automatic sub-second scaling
- Built-in HTTPS & domain routing
Single-purpose snippets invoked by HTTP or GCP pub/sub events.
- Opinionated runtime constraints
- Single concurrency per container
- Harder to test locally in bulk
- Best for async glue & background tasks
How Cloud Run Executes Requests
Key runtime mechanics: request-driven lifecycle, horizontal scaling, and concurrency multiplexing.
Unlike traditional FaaS where every single request requires its own container instance, Cloud Run multiplexes up to 80 concurrent requests per container instance by default.
After the initial container startup, incoming requests route to the warm instance with zero provisioning delay. Static Go binaries keep cold starts below 200 milliseconds.
Every deployed service receives an auto-provisioned https://*.a.run.app endpoint with managed Google SSL certificates, HTTP/2, and global Google Anycast ingress.
Core Services in Our Deployment Pipeline
How Google Cloud projects, Artifact Registry, IAM policies, and regions interact.
All infrastructure belongs to a single target Project ID. Both services are co-located in Jakarta (asia-southeast2) for minimal latency and local data residency in Indonesia.
Google Cloud's managed OCI container registry. Images built by Cloud Build are pushed here and streamed into Cloud Run instances via Google's internal private network backbone.
Public access is granted by binding roles/run.invoker to allUsers. Containers run under an unprivileged non-root service account with minimal IAM scope.
Monorepo Service Topology
Two decoupled, stateless container services deployed to Cloud Run in asia-southeast2.
Multi-stage Nginx container serving compiled static assets.
- Dynamic
$PORTbinding viaenvsubstat container boot - SPA history mode fallback:
try_files $uri /index.html - Reverse proxies
/api/*directly to backend$BACKEND_URL - Exposes
GET /healthzreturning HTTP 200
Statically linked Go binary packaged into Google Distroless.
- Sub-20MB image size with
CGO_ENABLED=0 - Purely stateless: scales to 0 and scales out horizontally
- Binds strictly to
0.0.0.0:$PORT - Graceful shutdown intercepting
SIGTERM/SIGINT
Cloud Run Runtime Invariants
Mandatory architectural requirements enforced by Google Cloud Run.
Cloud Run sets the PORT environment variable (default 8080).
- Server must bind to
0.0.0.0, never127.0.0.1 - Binding to localhost causes probe failure and container crash
- Nginx uses
nginx.conf.templatesubstituted at startup
Instances are ephemeral and scale to zero during periods of inactivity.
- Never write permanent files to container filesystem
- In-memory state is lost on scale-down or revision switch
- Auto-scaling: 0 min instances, 10 max instances
Cloud Run sends SIGTERM before killing an instance.
- 10-second termination grace period to drain active requests
- Go intercepts signals via
signal.NotifyContext - Executes
server.Shutdown()cleanly
Optimized Multi-Stage Containerization
Minimal attack surface, small image footprints, and non-root execution.
# Stage 1: Build static Go binary
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod go.sum* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-w -s" -o /server ./cmd/server
# Stage 2: Distroless non-root runner
FROM gcr.io/distroless/static-debian12:nonroot
WORKDIR /
COPY --from=builder /server /server
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["/server"]
# Stage 1: Build React Vite SPA
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Nginx with dynamic PORT substitution
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf.template
CMD envsubst '$PORT $BACKEND_URL' < \
/etc/nginx/conf.d/default.conf.template > \
/etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'
Step 1–4: Google Cloud Authentication & Setup
Terminal setup: authenticate with GCP, configure target project and Jakarta region, and provision Artifact Registry.
# 1. Authenticate with your Google account
gcloud auth login
# (Optional) Application Default Credentials for Terraform
gcloud auth application-default login
# 2. Configure target Project ID & Jakarta Region
gcloud projects list
export PROJECT_ID="YOUR_PROJECT_ID"
export REGION="asia-southeast2"
gcloud config set project "$PROJECT_ID"
gcloud config set run/region "$REGION"
# 3. Enable necessary Google Cloud APIs
gcloud services enable \
run.googleapis.com \
cloudbuild.googleapis.com \
artifactregistry.googleapis.com \
compute.googleapis.com
# 4. Create standard OCI Docker repository in Jakarta
gcloud artifacts repositories create roadto-jakarta \
--repository-format=docker \
--location="$REGION" \
--description="Road to DevFest Jakarta Docker repo"
# Configure local Docker credential helper
gcloud auth configure-docker "${REGION}-docker.pkg.dev" --quiet
Step 5–6: Deploying Go API & React SPA
Deploying the containerized backend from source, extracting its live URL, and injecting it into the frontend SPA.
# Deploy API from source with public ingress
gcloud run deploy roadto-jakarta-api \
--source ./apps/api \
--region "$REGION" \
--platform managed \
--allow-unauthenticated \
--port 8080
# Extract live HTTPS URL and verify /healthz
API_URL=$(gcloud run services describe roadto-jakarta-api \
--region "$REGION" --format='value(status.url)')
echo "Backend API: $API_URL"
curl -i "$API_URL/healthz"
curl -s "$API_URL/api/v1/event" | jq .
# Deploy Web SPA injecting live $API_URL
gcloud run deploy roadto-jakarta-web \
--source ./apps/web \
--region "$REGION" \
--platform managed \
--allow-unauthenticated \
--port 8080 \
--set-env-vars BACKEND_URL="$API_URL"
# Extract live Frontend URL and test end-to-end
WEB_URL=$(gcloud run services describe roadto-jakarta-web \
--region "$REGION" --format='value(status.url)')
echo "Frontend: $WEB_URL"
curl -i "$WEB_URL/healthz"
curl -s "$WEB_URL/api/v1/event" | jq .
Turnkey Orchestration via deploy.sh
How the repository helper script automates project discovery, dependency ordering, and URL injection.
- Environment Pre-flight: Runs
initto enable APIs and setup Artifact Registry. - Ordered Pipeline: Deploys
apps/apifirst, extracts live URL, and injectsBACKEND_URLintoapps/web. - IAM Invoker Policy: Automatically binds
roles/run.invokertoallUsersfor public access.
# 1. Initialize APIs & Artifact Registry
./deploy/deploy.sh init
# 2. Deploy both API and Web in correct dependency order
./deploy/deploy.sh all
# Or deploy individually:
./deploy/deploy.sh api
./deploy/deploy.sh web
# Custom Project & Region override:
GCP_REGION=asia-southeast2 \
PROJECT_ID=my-gcp-project \
./deploy/deploy.sh all
Google Cloud Build Automation
Fully automated serverless CI/CD pipeline defined in deploy/cloudbuild.yaml.
# Automatic: git push to main runs .github/workflows/deploy.yml
# Manual: submit build directly to Cloud Build
gcloud builds submit \
--config deploy/cloudbuild.yaml \
--substitutions=\
_REGION="asia-southeast2",\
_REPO_NAME="roadto-jakarta"
- No local Docker daemon required on developer workstations
- Build artifacts cached and tagged in Google Artifact Registry
- Automated via GitHub Actions (
.github/workflows/deploy.yml) on push - Zero-downtime rolling revision deployment with auto-rollback
Terraform & Knative Declarative Manifests
Managing Cloud Run services declaratively for version control and compliance.
resource "google_cloud_run_v2_service" "api" {
name = var.api_service_name
location = var.region
template {
scaling {
min_instance_count = 0
max_instance_count = 10
}
containers {
image = var.api_image
resources {
limits = { cpu = "1", memory = "256Mi" }
}
liveness_probe {
http_get { path = "/healthz" }
}
}
}
}
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: roadto-jakarta-api
spec:
template:
spec:
containers:
- image: asia-southeast2-docker.pkg.dev/...
resources:
limits:
cpu: 1000m
memory: 256Mi
livenessProbe:
httpGet:
path: /healthz
port: 8080
Cloud Monitoring: The 4 Golden Signals
Zero-instrumentation telemetry emitted automatically by Google Cloud Run without custom agents or Prometheus sidecars.
Measures request duration from Google Anycast ingress edge to client finish.
- Metric:
run.googleapis.com/request_latencies - Differentiates cold-start spikes from warm hits
- Target: <50ms p95 on warm Go API routes
Real-time request throughput across active service revisions.
- Metric:
run.googleapis.com/request_count - Group by response codes: 2xx, 4xx, and 5xx
- Monitors traffic routing across active revisions
Automatic tracking of failed requests and runtime exceptions.
- Alerting threshold: 5xx rate > 1% for 5 mins
- Google Cloud Error Reporting groups Go panics
- Notifications routed to Slack, Email & PagerDuty
Resource consumption against defined container limits.
- Metric:
container/memory/utilizations(256MiB limit) - Metric:
container/cpu/utilizations(1 vCPU limit) - Live instance count: autoscale from 0 to 10 instances
Structured Logging, Cloud Trace & Probes
Correlating distributed requests across microservices, querying Logs Explorer, and enforcing container health.
All stdout/stderr is automatically parsed into Cloud Logging:
{
"severity": "INFO",
"message": "Handled RSVP request",
"timestamp": "2026-10-24T10:00:00Z",
"logging.googleapis.com/trace":
"projects/PROJECT_ID/traces/TRACE_ID",
"httpRoute": "/api/v1/rsvp"
}
resource.type="cloud_run_revision"
AND resource.labels.service_name="roadto-jakarta-api"
AND severity>=WARNING
Propagate trace context headers across microservice boundaries:
Provides waterfall latency breakdown without manual APM agent installation.
Container verification contract via GET /healthz:
- Startup Probe: Holds user traffic during container boot (period 5s, 3 retries). Prevents HTTP 502s.
- Liveness Probe: Periodically verifies container responsiveness; restarts deadlocked instances.
# Check API health & live JSON
curl -i https://$API_URL/healthz
curl -s https://$API_URL/api/v1/event | jq .
# Check Frontend SPA & Nginx probe
curl -i https://$WEB_URL/healthz
curl -i https://$WEB_URL/
Production Deployment Checklist
Key architecture principles and command reference for Cloud Run services.
- Zero Cost When Idle: Scales to 0 instances when inactive with sub-second resumption.
- Secure Container Baseline: Non-root distroless container for the API; unprivileged Nginx for the Web.
- Dynamic Port Contract: Both services strictly honor dynamic
$PORTlistening on0.0.0.0. - Local Latency: Deployed in
asia-southeast2(Jakarta) for rapid regional response times.
./deploy/deploy.sh init./deploy/deploy.sh allgcloud builds submit --config deploy/cloudbuild.yamlterraform -chdir=deploy/terraform applycurl -i https://<SERVICE_URL>/healthzQ&A & Technical Discussion
Open floor for audience questions, architecture trade-offs, and implementation details.
- Min Instances vs Cost: Keeping 1 warm instance to eliminate cold starts on critical paths.
- Secret Management: Ingesting API keys via GCP Secret Manager runtime mounts.
- VPC Connectors: Connecting Cloud Run privately to Cloud SQL or Memorystore.
- Cloud Run vs GKE: At what scale does dedicated GKE become more cost-effective?
All source code and manifests are available in this repository:
apps/api/apps/web/deploy/deploy.shdeploy/cloudbuild.yamldeploy/terraform/
Road to DevFest Jakarta 2026. Deploying modern web apps and APIs to Google Cloud Run in asia-southeast2.