Continuous Deployment from GitHub to Google Cloud Run using Build Triggers

Published on .

I recently launched indieweb-endpoints.cc, a single-purpose Sinatra application for discovering a website’s IndieWeb-specific URLs. The website demonstrates the capabilities of the indieweb-endpoints Ruby gem and provides a JSON API accessible to other websites or command line utilities.

For instance, details of this website’s IndieWeb endpoints are available online or via the command line using curl:

curl --header 'Accept: application/json' 'https://indieweb-endpoints.cc/search?url=https://sixtwothree.org'

I deployed IndieWeb Endpoints to the recently-launched (in beta, anyway…) Google Cloud Run “serverless” container platform. As part of Google’s broader and infinitely complex Google Cloud suite of products, Cloud Run deploys Docker containers, allows for assigning custom domain names, and provisions auto-renewing TLS certificates using Let’s Encrypt. Cloud Run isn’t appropriate for everything, but it’s an interesting choice for a certain category of Web thang.

While this post is not an endorsement of Google Cloud or competing cloud-based service providers, it does detail solutions to a few specific problems I encountered when automating deployments from a GitHub repository to Cloud Run.

Assumptions

I want to lay out a few assumptions before proceeding:

  1. This post is first and foremost a brain dump for myself so that I don’t forget how to fix these problems next time I encounter them.
  2. The instructions below are accurate as of the date of publication. Google’s notorious for rapidly changing their products so there’s no guarantee that these instructions will work in the future. Hell, Google’s own documentation website isn’t entirely accurate.
  3. Most importantly, this post assumes you have some familiarity with Google Cloud, Docker, GitHub, etc. (see Assumption #1). The Cloud Run Quickstarts may be helpful if you haven’t yet configured an application on the platform.
  4. Let’s also assume you have a Google Cloud account with Cloud Run enabled, have built a Docker-ized application whose source code is on GitHub, and have done the initial setup on Cloud Run. I’ll be using the indieweb-endpoints.cc GitHub repository in the examples throughout this post.

That’s a lot, but… yeah. Trying to keep this post focused.

Create a Cloud Build configuration file

In the root of your project, create a cloudbuild.yaml file with contents similar to the following:

steps:
  - name: "gcr.io/cloud-builders/docker"
    args: ["build", "-t", "gcr.io/$PROJECT_ID/indieweb-endpoints-cc-web", "."]

  - name: "gcr.io/cloud-builders/docker"
    args: ["push", "gcr.io/$PROJECT_ID/indieweb-endpoints-cc-web"]

  - name: "gcr.io/cloud-builders/gcloud"
    args: ["beta", "run", "deploy", "indieweb-endpoints-cc-web", "--image", "gcr.io/$PROJECT_ID/indieweb-endpoints-cc-web", "--platform", "managed", "--region", "us-central1"]

images:
  - "gcr.io/$PROJECT_ID/indieweb-endpoints-cc-web"

The steps above instruct Cloud Build to build a tagged container image of the application, push it to Container Registry, and deploy the container image to Cloud Run.

A few things you’ll tweak:

  • The service name (indieweb-endpoints-cc-web in the snippet above) is the name of the Cloud Run service.
  • The platform should be either managed or gke.
  • The region should be one of the available Cloud Run regions (currently limited to us-central1).

Add, commit, and push this new file to your application’s GitHub repository.

Create a deployable branch

I prefer deploying to production environments from a branch other than master:

git checkout -b production
git push -u origin production

Create a build trigger

Google’s Automating builds using build triggers tutorial actually does a good job of covering this step. Basically, create a build trigger using your application’s GitHub repository (specifically, the production branch) and the new cloudbuild.yaml file.

You should then be able to trigger builds by pushing new commits to the production branch or manually using Google’s website. Here’s where things went sideways for me.

Update Cloud Build service account permissions

The following steps will address the first error I encountered during a failed deploy:

ERROR: (gcloud.beta.run.deploy) PERMISSION_DENIED: The caller does not have permission

  1. Log in to the Console and navigate to the IAM page.
  2. Click the “edit” icon next to your project’s Cloud Build Service Account (e.g. $PROJECT_NUMBER@cloudbuild.gserviceaccount.com).
  3. In the “Edit permissions” panel, click “Add another role” and choose “Cloud Run Admin.”
  4. Click “Save.”

If you retry the earlier failed deploy and have the same problem I had, you’ll need to…

Update service account user access

The next error you encounter might look like:

Permission ‘iam.serviceaccounts.actAs’ denied on service account $PROJECT_NUMBER-compute@developer.gserviceaccount.com (or it may not exist).

(Here and elsewhere, $PROJECT_NUMBER stands in for the unique string of characters specific to your project.)

This one took forever to sort out despite staring intently at not one but two Stack Overflow answers. I’ll reserve my Very Strong Feelings™ on cloud platform service provider UX design for another time…

  1. Log in to the Console and navigate to the Service Accounts page.
  2. Click the checkbox next to “$PROJECT_NUMBER-compute@gserviceaccount.com” and, if it’s not already visible, click “Show Info Panel.”
  3. Click “Add Member.”
  4. Enter “$PROJECT_NUMBER@cloudbuild.gserviceaccount.com” into New Members and from “Select a Role,” choose “Service Account User.”
  5. Click “Save.”

Retry the failed deploy and…

Continuous Deployment!

You should now be set up to continuously deploy an application from a production branch on GitHub to Google Cloud Run. Next step? Figure out how to post notifications to Slack.