Terraform’s ability to manage Cloud Run services isn’t just about provisioning infrastructure; it’s about achieving declarative, version-controlled deployments of your containerized applications on Google Cloud.

Here’s a Cloud Run service definition in Terraform:

resource "google_cloud_run_v2_service" "default" {
  name     = "my-cloud-run-app"
  location = "us-central1"
  project  = "my-gcp-project-id"

  template {
    containers {
      image = "us-docker.pkg.dev/cloudrun/container/hello"
      ports {
        container_port = 8080
      }
    }
    scaling {
      min_instance_count = 0
      max_instance_count = 5
    }
  }

  lifecycle {
    ignore_changes = [
      # Ignore changes to the latest ready revision, as it's managed by Cloud Run
      # when new deployments occur. We only want to manage the service configuration
      # itself, not the specific revision hash.
      template[0].latest_ready_revision
    ]
  }
}

resource "google_cloud_run_v2_job" "default" {
  name     = "my-cloud-run-job"
  location = "us-central1"
  project  = "my-gcp-project-id"

  template {
    template {
      containers {
        image = "us-docker.pkg.dev/cloudrun/container/hello"
      }
    }
    parallelism = 1
    task_count  = 1
  }
}

resource "google_cloud_run_domain_mapping" "default" {
  location = "us-central1"
  project  = "my-gcp-project-id"
  name     = "my-app.example.com"

  spec {
    route_name = google_cloud_run_v2_service.default.name
  }
}

This Terraform code defines three core Cloud Run resources:

  • google_cloud_run_v2_service: This is the primary resource for deploying a long-running web application. It specifies the container image to use, the port the container listens on, and scaling parameters like min_instance_count and max_instance_count. The lifecycle block with ignore_changes is crucial; it tells Terraform not to worry about tracking the exact latest_ready_revision because Cloud Run itself updates this when new code is deployed, and we want Terraform to focus on the desired state of the service configuration, not the specific deployed artifact’s hash.
  • google_cloud_run_v2_job: This resource is for running one-off or batch tasks. It also defines the container image but includes parameters like parallelism and task_count to control how the job runs.
  • google_cloud_run_domain_mapping: This resource associates a custom domain (my-app.example.com) with your Cloud Run service, making it accessible via your branded URL instead of the default .run.app address.

Terraform manages the lifecycle of these resources. When you run terraform apply, Terraform communicates with the Google Cloud API to create, update, or delete the Cloud Run services, jobs, and domain mappings to match the configuration in your .tf files. This provides a powerful way to automate your application deployments and infrastructure management, ensuring consistency and reproducibility.

The real magic happens when you integrate this with a CI/CD pipeline. Imagine a GitHub Actions workflow that triggers on a git push to your main branch. This workflow would run terraform init, terraform plan, and finally terraform apply. If the plan shows changes (e.g., a new container image tag, updated environment variables, or adjusted scaling settings), terraform apply will execute the necessary API calls to update your Cloud Run service without manual intervention. This means your application is updated automatically whenever new code is merged.

The mental model for Cloud Run with Terraform is one of desired state. You declare what your Cloud Run service should look like – its image, its traffic settings, its scaling behavior, its environment variables, its VPC connector, its authentication requirements – and Terraform ensures the actual state in Google Cloud matches your declaration. When you want to update the image, you change the image attribute in your Terraform file to point to a new container tag (e.g., my-repo/my-app:v1.1.0) and run terraform apply. Terraform detects the difference and orchestrates the deployment of the new revision to Cloud Run.

One of the most powerful, yet often overlooked, aspects of managing Cloud Run with Terraform is its ability to handle IAM policies and service accounts declaratively. You can define the google_cloud_run_v2_service_iam_member or google_cloud_run_v2_service_iam_binding resources to grant specific IAM roles (like roles/run.invoker) to service accounts or even allUsers (for public access). This means your infrastructure code not only deploys the application but also explicitly defines who can access it, ensuring security policies are version-controlled and auditable alongside your application code. You can also associate a specific service account with the Cloud Run service using the service_account argument within the template block, ensuring your application runs with the least privilege necessary.

When you deploy a new container image to an existing Cloud Run service via Terraform, what actually happens is Terraform calls the Cloud Run API to create a new revision of the service. It then updates the service’s traffic configuration to point 100% of traffic to this new revision. Cloud Run then gracefully rolls out this new revision, gradually shifting traffic and ensuring that any existing requests are completed on the old revision before terminating its instances. This managed rollout is a key benefit of Cloud Run, and Terraform simply tells Cloud Run when to initiate this process based on your code changes.

The next step after mastering service deployment is managing the finer details of networking, such as configuring VPC connectors for private access or setting up granular traffic splitting for canary deployments, all within Terraform.

Want structured learning?

Take the full Cloud-run course →