12 — Cloud Run
← Previous: 11 — Service Accounts & IAM
Cloud Run is where your Django app runs. This chapter creates the Cloud Run service with Terraform and explains all the configuration options.
Cloud Run basics
Cloud Run takes a Docker image and runs it as a service. It:
- Starts when requests come in (or stays at 0 instances if --min-instances=0)
- Scales up automatically under load
- Scales to zero when idle (no cost)
- Handles HTTPS automatically
Create the Cloud Run service
Add to infrastructure/main.tf:
# Cloud Run service for the web app
resource "google_cloud_run_service" "web" {
name = "mycoolproject"
location = var.region
template {
spec {
# Which service account this container runs as
service_account_name = google_service_account.run.email
# Container configuration
containers {
image = "${google_artifact_registry_repository.app.repository_url}/app:latest"
ports {
container_port = 8080
}
# Environment variables from secrets
env {
name = "DJANGO_SETTINGS_MODULE"
value = "core.settings.prod"
}
env {
name = "PORT"
value = "8080"
}
env {
name = "PYTHONUNBUFFERED"
value = "1"
}
# Secret values injected as env vars
# The secret name must match what's in Secret Manager
# The env var name is what Django reads
env {
name = "DATABASE_URL"
value_from {
secret_key_ref {
name = "DATABASE_URL"
secret_key = "latest"
}
}
}
env {
name = "SECRET_KEY"
value_from {
secret_key_ref {
name = "DJANGO_SECRET_KEY"
secret_key = "latest"
}
}
}
env {
name = "ALLOWED_HOSTS"
value = "mycoolproject.com,www.mycoolproject.com"
}
}
# Scaling configuration
min_scale = 0
max_scale = 5
# Resource limits
resources {
limits {
cpu = "1"
memory = "512Mi"
}
}
}
# Timeout (max request duration)
timeout = "60s"
}
# Allow public access (no authentication)
traffic {
revision_suffix = "initial"
percent = 100
}
}
# Make Cloud Run service publicly accessible
resource "google_cloud_run_service_iam_member" "web_public" {
location = google_cloud_run_service.web.location
service = google_cloud_run_service.web.name
role = "roles/run.invoker"
member = "allUsers"
}
output "web_service_url" {
value = google_cloud_run_service.web.status[0].url
}
Run terraform apply to create the service.
Key configuration explained
service_account_name
The container runs as this service account, which has permissions to: - Read secrets - Access Cloud Storage - Enqueue Cloud Tasks
image
Points to the image in Artifact Registry. We use app:latest for now, but GitHub Actions will deploy with a <git-sha> tag for rollbacks.
ports
Cloud Run expects port 8080 by default. We explicitly set it.
env from secrets
Instead of --set-secrets in gcloud, we define env vars from secrets directly in Terraform:
env {
name = "DATABASE_URL"
value_from {
secret_key_ref {
name = "DATABASE_URL"
secret_key = "latest"
}
}
}
min_scale = 0 (scale to zero)
When there's no traffic, Cloud Run scales to zero instances. This means no cost. The first request after idle takes 1-2 seconds to start (cold start).
max_scale = 5
Hard cap on instances. Prevents runaway scaling from a traffic spike.
timeout = "60s"
Max duration for a single request. If a request takes longer, Cloud Run kills it.
resources
CPU and memory limits. Django with Gunicorn typically needs 512Mi and 1 CPU.
Update image after GitHub Actions builds
After GitHub Actions builds and pushes the image with a <git-sha> tag, we update Cloud Run:
gcloud run services update mycoolproject \
--image=southamerica-east1-docker.pkg.dev/mycoolproject-prod/app-repo/app:<git-sha> \
--region=southamerica-east1
Or in Terraform, we can set the image dynamically, but it's common to manage the deployed image tag via GitHub Actions (not Terraform) since Terraform apply is slow and GitHub Actions deploys on every push.
Health check endpoint
Cloud Run needs a health check to know if the container is running. Django should have a /health/ endpoint that returns {"status": "ok"}.
In web/core/urls.py:
from django.http import JsonResponse
urlpatterns = [
# ... other routes ...
path("health/", lambda request: JsonResponse({"status": "ok"})),
]
Update on deploy
When you push code, GitHub Actions:
1. Builds Docker image
2. Pushes to Artifact Registry with <git-sha> tag
3. Updates Cloud Run to use the new image
gcloud run services update mycoolproject \
--image=<registry-url>/app:<git-sha> \
--region=southamerica-east1
Cloud Run creates a new revision and gradually shifts traffic (or shifts all at once for simplicity in this guide).
Verify the service
# Get the service URL
gcloud run services describe mycoolproject --region=southamerica-east1 --format="value(status.url)"
# Test it
curl <url>/health/
Navigation
- 01 — Introduction: What We're Building
- 02 — Terraform Overview
- 03 — Cloud Services Explained
- 04 — PlanetScale Database Explained
- 05 — Project Setup & Terraform State
- 06 — GCP Project & APIs
- 07 — Artifact Registry
- 08 — Secrets Management
- 09 — Cloud Storage
- 10 — Service Accounts & IAM
- 11 — Cloud Run (Current chapter)
- 12 — Cloud Tasks & Scheduler
- 13 — Dockerfile
- 14 — First Deploy
- 15 — Custom Domain & SSL
- 16 — Workload Identity Federation
- 17 — GitHub Actions CI/CD
- 18 — Quick Reference