Quickstart: Continuous deployment to Google Cloud Run using Github Actions
Tags: github, serverless, googlecloud, tutorial
Published: 2020-11-12 23:09:00

## Preamble

If you’re a hobbyist developer and you want to host your fun app for [next-to-free](https://dev.to/pcraig3/cloud-run-vs-app-engine-a-head-to-head-comparison-using-facts-and-science-1225), you should definitely use Google [Cloud Run](https://cloud.google.com/run). Once you set it up, Cloud Run is a dream to work with, but getting started can mean a lot of trial and error.

In this post, I will go step-by-step through setting up [GitHub Actions](https://github.com/features/actions) to automatically deploy your cool app to Cloud Run.

Let’s get started.

## Prerequisites

### 1. Install `gcloud` and do a local deployment

My initial idea was to write a “how to get started with Cloud Run” post, but it turns out [the official Quickstart](https://cloud.google.com/run/docs/quickstarts/build-and-deploy) is excellent.

If you’re just starting out, follow Google’s Quickstart. It covers:

- [Installing and initializing the Google Cloud SDK](https://cloud.google.com/sdk/docs/install)
- Creating your first project on Google Cloud Platform (GCP)
- Building and pushing a container
- Deploying your container to Cloud Run

By the end of the tutorial, you will have installed the `gcloud` command-line interface (CLI) and used it to deploy your first container to Cloud Run. **This article picks up after the Quickstart material**: it’s intended for anyone looking to add a bit more automation to their deployments.

### 2. Create a GitHub repository

You will also need a GitHub repository — you know, for the GitHub Actions to work. If you’re more of a “see the code”  learner, I created an example repo — [pcraig3/hello-cr](https://github.com/pcraig3/hello-cr) — as part of writing the article. Feel free to clone or fork it.

## Service accounts

When you run `gcloud` commands locally, you’re probably using your root account (ie, tied to your email address) because that’s the default account you create when you sign up to GCP. For local development, that’s fine: your root account is a superuser with lots of permissions, so you can do whatever you need to.

However, you should really avoid using your root account on  other platforms. If your account password is ever stolen, an evildoer can take over your entire G-Cloud setup and use it to host [crappy websites](https://theannoyingsite.com), run up a huge tab, or steal your data. 

Instead, you should [create a "service account"](https://cloud.google.com/iam/docs/understanding-service-accounts): a separate account with limited permissions intended for specialized tasks. If your service account is compromised, the aforementioned evildoer can take down your Cloud Run app, but they won’t be able to mess up too much else.

### Creating a service account using the Console

I’m most familiar with creating service accounts by logging in and clicking around, so that’s what I’m going to do here. If you’re smarter than me, you might prefer the command line, but all the same stuff will apply.

- Log in to [Google’s backend console](https://console.cloud.google.com/home/dashboard)
- Using the drop-down selector in the header, pick the project where your Cloud Run app lives. (Mine is called `hello-cr`, it’ll be in a few screenshots)

![Project: hello-cr](https://dev-to-uploads.s3.amazonaws.com/i/r6i6px4106xoxv0e1w1c.png)

- Open the left-hand navigation menu and select "IAM & Admin" > "Service Accounts"

![Service accounts](https://dev-to-uploads.s3.amazonaws.com/i/zhk7ds0yu19356p6et7s.png)

- Click "+ Create service account": it will be in the contextual header near the top of the screen
- The first section is titled "Service account details". Enter a "Service account name" (it will also be used for the id). Adding a description is optional. Click "Create" when you are finished.

![Service account details](https://dev-to-uploads.s3.amazonaws.com/i/6s589mkdno1f682qjoft.png)

- Next, assign permissions to your new service account. This part was pretty tricky to figure out, but after a lot of trial and error, I found this combination works.
- Add the following permissions: 
  - `Cloud Run Admin`
  - `Cloud Run Service Agent`
  - `Cloud Build Service Agent`
  - `Viewer`
- Click "Continue"

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/769naftauggot8kek7ex.png)

- The final section ("Grant users access to this service account") is not relevant to this tutorial, so you can skip it.

Hooray! Now that you’ve created your service account, let’s make sure that everything works as expected by trying a local deployment as your new service account.

### Authenticating on the command line as a service account

Show all of your service accounts with 

- `gcloud iam service-accounts list`

The account you just created should be listed there. 

In order to run commands as the service account, we will need to download a [service account key](https://cloud.google.com/iam/docs/creating-managing-service-account-keys). The service account key is a JSON file containing credentials for the account, so be careful with it (eg, don’t commit it to your repository 😬).

Get the service account key with

- `gcloud iam service-accounts keys create ./NAME-OF-KEY-FILE.json --iam-account EMAIL-ADDRESS`

Replace `EMAIL-ADDRESS` with the email for your service account. Also, you probably want to rename the `.json` file to something less shouty. If you store it in your app directory, make sure to add it to your `.gitignore` file.

Now that you have the key file, you can authenticate yourself as this service account.

Authenticate yourself with

- `gcloud auth activate-service-account --key-file=NAME-OF-KEY-FILE.json`

This way, you can run commands as the service account. 

You can see which account you are logged into with

-  `gcloud auth list` 

You can change back to your original account at any time with

- `gcloud config set account YOUR-EMAIL-ADDRESS`

### Deploying your app with your service account

You should be able to build and deploy your app using your service account. To do so, follow the steps outlined in the [Cloud Run Quickstart](https://cloud.google.com/run/docs/quickstarts/build-and-deploy#containerizing).

> Build your container image using Cloud Build, by running the following command from the directory containing the Dockerfile:
> 
> `gcloud builds submit --tag gcr.io/PROJECT-ID/helloworld`
> 
> where PROJECT-ID is your GCP project ID.

Once built, let’s deploy.

> Deploy using the following command:
> 
> `gcloud run deploy --image gcr.io/PROJECT-ID/helloworld --platform managed`
> 
> Replace PROJECT-ID with your GCP project ID. You can view your project ID by running the command gcloud config get-value project.

Nice! If the deploy succeeded, you know this service account has the permissions it needs to automate future deployments.

### Setting up a "deploy" GitHub Action

Phew, now we’re on the home stretch. 

[Github Actions](https://github.com/features/actions) automate various parts of your development workflow: you can run tests, do linting, or trigger notifications based on certain conditions. You can also set up rules to deploy your app when your `main` branch is updated, which is what we want to do.

[GitHub maintains a Marketplace of third-party Actions](https://github.com/marketplace) that we can use to plug into third-party services. Luckily for us, the folks at GCP have built a GitHub Action we can use to log in and run CLI commands: [https://github.com/marketplace/actions/setup-gcloud-environment](https://github.com/marketplace/actions/setup-gcloud-environment) 

[GitHub Actions’ syntax](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions) is `.yml`-based, so let’s see what our deploy workflow will look like.

In the root directory of your app, create a file at `.github/workflows/deploy.yml`.

```yml
# .github/workflows/deploy.yml
name: Deploy to Cloud Run
on:
  push:
    branches:
      - main
env:
  PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
  RUN_REGION: us-east1
  SA_KEY_JSON: ${{ secrets.GCP_SA_KEY_JSON }}
jobs:
  deploy:
    name: Deploy to Cloud Run
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      # Setup gcloud CLI
      - uses: GoogleCloudPlatform/github-actions/setup-gcloud@master
        with:
          version: "290.0.1"
          service_account_key: ${{ secrets.GCP_SA_KEY_JSON }}
          project_id: ${{ secrets.GCP_PROJECT_ID }}

      # Build and push image to Google Container Registry
      - name: Build
        run: gcloud builds submit --tag gcr.io/$PROJECT_ID/$PROJECT_ID:$GITHUB_SHA

      - name: Deploy
        run: gcloud run deploy $PROJECT_ID --image gcr.io/$PROJECT_ID/$PROJECT_ID:$GITHUB_SHA --platform managed --region $RUN_REGION

```

The `Build` and `Deploy` commands are pretty similar to those we just ran above, although there are a couple of differences. Let’s take a closer look at the build command.

```yml 
gcloud builds submit --tag gcr.io/$PROJECT_ID/$PROJECT_ID:$GITHUB_SHA
```

- The dollar-sign syntax (eg, `$PROJECT_ID`) represents an [environment variable](https://docs.github.com/en/free-pro-team@latest/actions/reference/environment-variables). All of our env vars are set at the top of the file, except `$GITHUB_SHA`, which is [a default environment variable](https://docs.github.com/en/free-pro-team@latest/actions/reference/environment-variables#default-environment-variables) referencing the current commit hash
- `gcr.io/$PROJECT_ID/$PROJECT_ID`: My project name is also the same as my app name (`hello-cr`). If your "project" name is different from your app name, you will need to add a new variable.
- `:$GITHUB_SHA`: Adding a colon and a string is a way of [tagging containers](https://docs.docker.com/engine/reference/commandline/tag/) so we can distinguish between versions. In this case, the git SHA is appended, so that we can identify the current version of the app by referencing our commit history.

#### Setting up app secrets 

[If Actions are enabled for your repo](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/disabling-or-limiting-github-actions-for-a-repository), pushing your new `deploy.yml` file will attempt a deployment but it will fail because we need to add the secrets for our service account. The secrets we need to add are referred to in the `env:` section near the top of the file.

```yml
env:
  PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
  RUN_REGION: us-east1
  SA_KEY_JSON: ${{ secrets.GCP_SA_KEY_JSON }}
```

The `RUN_REGION` is hard-coded, but we have to create the other two secrets. [Github’s documentation for creating secrets](https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository) is actually really good: you can add them under "Settings" > "Secrets" > "New secret".

1. `GCP_PROJECT_ID`: The project ID is just a string
2. `GCP_SA_KEY_JSON`: For the service account key, copy the entire JSON file into the textarea. It seems a bit weird but it works just fine.

Secrets are encrypted as soon as they are created, and there’s no way to reveal the original values once saved, so if you are adding sensitive values in the future, make sure to add them as secrets rather than accidentally committing them to your repo.

Once you’ve created your secrets and committed your `deploy.yml` file, your next push to the `main` branch will  trigger an automated deployment. It usually takes a few minutes to deploy a new version of your application (depending mostly on how long it takes to build your container).

#### [`pcraig3/hello-cr`](https://github.com/pcraig3/hello-cr) on GitHub

While writing this post, I created an example repository using [Google’s Node.JS sample application]((https://cloud.google.com/run/docs/quickstarts/build-and-deploy#writing)) and added [my workflow file]((https://github.com/pcraig3/hello-cr/blob/main/.github/workflows/deploy.yml)) to it. It’s super simple, and might help you out if you need an tangible example of these concepts. Feel free to clone, fork, or [raise an issue](https://github.com/pcraig3/hello-cr/issues) if you have any questions.

{% github pcraig3/hello-cr %}


#### All done! 

You did it! Get yerself light lager and a pizza. 🍕