Deploying to Google Cloud Run with Terraform
Tags: terraform, googlecloud, devops
Published: 2021-03-15 07:47:38

---
title: 'Deploying to Google Cloud Run with Terraform'
published: true
description: 
tags:
  - terraform
  - googlecloud
  - devops
cover_image: https://hyperfoo.io/images/terraform-01.png
canonical_url: https://ruanmartinelli.com/posts/terraform-cloud-run
---

[Cloud Run](https://cloud.google.com/run) is a serverless platform from [Google Cloud](https://cloud.google.com/) to deploy and run containers. It's fully managed, autoscallable, and has a generous [free tier](https://cloud.google.com/run/pricing). Cloud Run can be used to serve Restful web APIs, WebSocket applications, or microservices connected by gRPC. It also integrates well with other Google Cloud solutions such as Cloud Tasks, Cloud Scheduler, and Pub/Sub.

[Terraform](https://www.terraform.io/) is a popular open-source tool for running infrastructure as code. Terraform lets you manage and deploy infrastructure from multiple providers, one of them being Google Cloud. If this is your first time reading about Terraform, you might wanna check [this introduction](https://hyperfoo.io/posts/introduction-to-terraform) first.

In this article, you will see how to deploy a Cloud Run service to Google Cloud using Terraform. All the infrastructure will be written using HCL, the native syntax for Terraform's language. By the end of the tutorial, you should have a service up and running on Cloud Run and a URL to access it.

## Prerequisites

To follow this tutorial you will need:

- Terraform CLI. I recommend using the latest version, currently v0.14. Instructions to download and install Terraform can be found [here](https://www.terraform.io/downloads.html).
- Google Cloud SDK. The most recent version should also work well for this tutorial. Installation instructions [here](https://cloud.google.com/sdk/docs/install).
- A Google Cloud account. If you don't have one, create it [here](https://console.cloud.google.com/getting-started).

## Initial setup

Start by authenticating the SDK to Google Cloud:

```bash
# Authenticate to Google Cloud
gcloud auth application-default login
```

Follow the web flow to obtain the access credentials. By the end of this step, you will be able to execute commands on the SDK similar to a service account.

Create a new project where your Cloud Run service will be deployed. Replace `PROJECT_ID` and `PROJECT_NAME` with the desired values:

```bash
gcloud projects create "PROJECT_ID" --name="PROJECT_NAME"
```

Create a folder with a HCL file inside:

```bash
mkdir my-service && cd my-service

touch main.tf
```

For simplicity, all the Terraform code in the next steps will be added to `main.tf`.

## Creating your first service

Begin by adding the requirements for Terraform and the Google provider on `main.tf`:

```ruby
# main.tf

terraform {
  required_version = ">= 0.14"

  required_providers {
    # Cloud Run support was added on 3.3.0
    google = ">= 3.3"
  }
}
```

This will require the Terraform version to be the latest and the Google provider to be at least on version `3.3` - when Cloud Run support was added.

Now add the Google provider configuration. Replace `PROJECT_ID` with the value from the previous step:

```ruby
# main.tf

provider "google" {
  # Replace `PROJECT_ID` with your project
  project = "PROJECT_ID"
}
```

The Cloud Run API doesn't come enabled on projects by default. Add the following resource to enable it:

```ruby
# main.tf

# Enables the Cloud Run API
resource "google_project_service" "run_api" {
  service = "run.googleapis.com"

  disable_on_destroy = true
}
```

Now create the Cloud Run service in the `us-central1` region:

```ruby
# main.tf

# Create the Cloud Run service
resource "google_cloud_run_service" "run_service" {
  name = "app"
  location = "us-central1"

  template {
    spec {
      containers {
        image = "gcr.io/google-samples/hello-app:1.0"
      }
    }
  }

  traffic {
    percent         = 100
    latest_revision = true
  }

  # Waits for the Cloud Run API to be enabled
  depends_on = [google_project_service.run_api]
}
```

Let's stop for a while and check what the code above is doing:

- `name`: the name of your service. It will be displayed in the public URL.
- `location`: the region where your service will run. See all the options [here](https://cloud.google.com/run/docs/locations).
- `image`: The Docker image that will be used to create the container. Cloud Run has direct support for images from the [Container Registry](https://cloud.google.com/container-registry) and [Artifact Registry](https://cloud.google.com/artifact-registry).
- `traffic`: controls the traffic for this revision. The `percent` property indicates how much traffic will be redirected to this revision. `latest_revision` specifies that this traffic configuration needs to be used for the latest revision.
- `depends_on`: waits for a resource to be ready, in this case, the Cloud Run API.

## Invoking the service

By default, Cloud Run services are private and secured by [IAM](https://cloud.google.com/iam). To access them, you would need valid credentials with at least the _Cloud Run Invoker_ permission set.

Let's change that and make the service publicly available through an HTTP endpoint.

> There are other ways than HTTP requests to trigger a service. For example, they can be accessed by gRPC requests, WebSockets, and other Google Cloud products like Cloud Scheduler.

Add the following code on `main.tf` to expose your service:

```ruby
# main.tf

# Allow unauthenticated users to invoke the service
resource "google_cloud_run_service_iam_member" "run_all_users" {
  service  = google_cloud_run_service.run_service.name
  location = google_cloud_run_service.run_service.location
  role     = "roles/run.invoker"
  member   = "allUsers"
}
```

The resource above is adding the permission to invoke the service to anyone on the internet. The `allUsers` identifier is a special value that represents authenticated and unauthenticated users.

To display the service URL in the Terraform command output, add this output to the configuration:

```ruby
# main.tf

# Display the service URL
output "service_url" {
  value = google_cloud_run_service.run_service.status[0].url
}
```

## Deploying the infrastructure

At this point, you have all it takes to deploy the infrastructure to Google Cloud using Terraform.

Start by initializing the configuration. Run the following command in your terminal:

```bash
# Initialize Terraform configuration
terraform init
```

Run `terraform plan` to verify the changes that will be applied:

```bash
# Plan the changes
terraform plan
```

If everything is correct, you will see that **3 resources** will be created and the service URL will be displayed.

```bash
Plan: 3 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + service_url = (known after apply)
```

Run `terraform apply` to apply all the changes:

```bash
# Apply all the changes
terraform apply
```

If everything goes well, you will see this at the end of the output:

```bash
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

service_url = https://app-cvmew7554a-uc.a.run.app
```

You can check if the service is running using `curl`:

```bash
curl https://app-cvmew7554a-uc.a.run.app

Hello, world!
Version: 1.0.0
Hostname: localhost
```

## Updating the service

Terraform can be used not just to push your initial infrastructure to Cloud Run, but also to update it.

Cloud Run works with _revisions_. When a configuration is changed or a new image is added, a new revision is created as a result. You can then redirect all the traffic to the new revision and start serving your updated application.

To update your service, simply change the value in the `image` property and pass it a new image:

```ruby
# main.tf

resource "google_cloud_run_service" "run_service" {
  name = "app"

  # ...

  template {
    spec {
      containers {
        # Change `hello-app:1.0` to `hello-app:2.0` 👇
        image = "gcr.io/google-samples/hello-app:2.0"
      }
    }
  }

  # ...
}
```

With this, Terraform will create a new revision on Cloud Run. The previous revision is preserved, but because of the `traffic` options defined previously, it won't recieve any traffic.

Run `terraform apply` to deploy the changes:

```bash
# Apply all the changes
terraform apply
```

You can check that the new image is live using `curl`:

```bash
curl https://app-cvmew7554a-uc.a.run.app

Hello, world!
Version: 2.0.0 # Now serving 2.0.0
Hostname: localhost
```

## Cleaning up

To delete all resources created with Terraform, run the following command and confirm the prompt:

```bash
# Destroy all the infrastructure created by Terraform
terraform destroy
```

This will disable the Cloud Run API, delete the Cloud Run service and its permissions.

The project was created using the `gcloud` CLI tool, so you will need to delete it manually. For that, you can run:

```bash
# Remove the Google Cloud project
gcloud projects delete PROJECT_ID
```

--

Thanks for reading this far!

Checkout [hyperfoo.io](https://hyperfoo.io) for more Terraform articles!