Deploying Serverless Containers with Cloud Run and Terraform

Cloud Computing
Date:October 29, 2026
Topic:
Deploying Serverless Containers with Cloud Run and Terraform
⏱ 3 min read

You push code. Google runs it. No clusters to patch, no nodes to size, no cold VMs burning budget while you sleep. That is the promise of Cloud Run, and when you pair it with Terraform, the promise becomes repeatable infrastructure you can review in a pull request.

Why Serverless Containers Change the Game

Traditional container orchestration forces you to think like an operator: node pools, autoscaler thresholds, pod disruption budgets. Cloud Run flips the model. You hand it a container image and a request concurrency target. The platform handles scaling from zero to thousands of instances in seconds, billing only for vCPU and memory while requests are in flight.

"

Cloud Run lets developers ship containers without becoming Kubernetes administrators.

— Kelsey Hightower

Prerequisites

You need a GCP project with billing enabled, the gcloud CLI authenticated, Terraform v1.6+, and Docker installed locally. Enable the Cloud Run, Artifact Registry, and Cloud Build APIs before you start.

bash
gcloud services enable run.googleapis.com artifactregistry.googleapis.com cloudbuild.googleapis.com

Terraform Module Structure

Keep the root module thin. Declare the provider, a random suffix for unique names, and call a child module that encapsulates the Cloud Run service, Artifact Registry repository, and IAM bindings.

hcl
module "api" {
  source       = "./modules/cloud-run-service"
  project_id   = var.project_id
  region       = var.region
  service_name = "orders-api"
  image_url    = "${var.region}-docker.pkg.dev/${var.project_id}/apps/orders-api:latest"
  cpu          = 1
  memory       = "512Mi"
  min_instances = 0
  max_instances = 100
}

The Child Module

The module creates an Artifact Registry repository if it does not exist, then defines the Cloud Run service with a revision template that pins CPU, memory, concurrency, and scaling limits. Notice the lifecycle block: it prevents Terraform from replacing the service on every image tag change, letting Cloud Run manage revisions natively.

hcl
resource "google_artifact_registry_repository" "repo" {
  location      = var.region
  repository_id = "apps"
  format        = "DOCKER"
}

resource "google_cloud_run_v2_service" "service" {
  name        = var.service_name
  location    = var.region
  project     = var.project_id
  template {
    containers [{
      image = var.image_url
      resources {
        limits = {
          cpu    = "${var.cpu}"
          memory = var.memory
        }
      }
    }]
    scaling {
      min_instance_count = var.min_instances
      max_instance_count = var.max_instances
    }
    concurrency = 80
  }
  lifecycle {
    ignore_changes = [template[0].containers[0].image]
  }
}
💡
TipSet min_instances > 0 only for latency-sensitive user-facing endpoints. Background workers should stay at zero.

CI/CD Integration

Cloud Build or GitHub Actions can build the image, push to Artifact Registry, and run terraform apply against the same module. Because the Terraform state ignores the image tag, each pipeline run creates a new Cloud Run revision instantly without touching the service resource.

yaml
steps:
- name: 'gcr.io/cloud-builders/docker'
  args: ['build', '-t', '$_IMAGE', '.']
- name: 'gcr.io/cloud-builders/docker'
  args: ['push', '$_IMAGE']
- name: 'hashicorp/terraform'
  args: ['apply', '-auto-approve']
  env: ['GOOGLE_CREDENTIALS=${_SA_KEY}']

Observability Out of the Box

Cloud Run emits structured logs to Cloud Logging and metrics to Cloud Monitoring automatically. Add the OpenTelemetry Go or Python SDK to your container for distributed traces. No sidecars, no DaemonSets, no extra cost.

MetricUse Case
request_countThroughput & error rate
request_latenciesP99 latency alerts
instance_countScaling behavior
cpu_utilizationRight-sizing containers

Security Hardening Checklist

Restrict ingress to internal load balancer or VPC connector. Enforce binary authorization. Use secret manager references instead of env vars. Grant the runtime service account least-privilege IAM roles.

hcl
ingress = "INGRESS_TRAFFIC_INTERNAL_ONLY"
vpc_access {
  connector = google_vpc_access_connector.connector.name
  egress    = "PRIVATE_RANGES_ONLY"
}

✦

Next Steps

Clone the official Terraform module, fork it, and add your organization's naming conventions and label policies. Wire the module into your GitOps pipeline. Then push a feature branch, watch the revision roll out, and verify zero-downtime traffic split in the Cloud Run console. You just deployed serverless containers like a platform team, not a sysadmin.

Share𝕏 Twitterin LinkedInin Whatsapp