Publish your Cloud Run App with GitHub Actions

A very fast way to deploy your application with GitHub

Photo by Allan Nygren on Unsplash.
Photo by Allan Nygren on Unsplash.

Google recently announced Cloud Run, a new Google Cloud Platform (GCP) feature, to deploy your Docker applications fast and easily. This guide will explain how to build and deploy a simple static application using the new continuous integration/continuous delivery system provided by GitHub: Actions.

To make this project live, we’ll use the following tools:

  • Google Cloud Run, to execute our Docker containers
  • Google Cloud Container Registry to store our Docker images
  • GitHub Actions to manage the continuous deployment
  • GitHub to store the source code of our project.

Configure the GCP Project

Service account creation

The first step is to create a service account which will allow us to connect from GitHub actions. To do so, you can click the “Create Service Account” button in the “IAM & Admin/Service Accounts” menu in the GCP interface. Now fill in the “Service Account Name” field with the value “GitHub-actions” and the “Service Account Description” field with the value “Account used by GitHub Actions to connect with GCP.”

At this point, keep the value generated in the field Service account ID. It is the service account username that will be used later. It should look like github-actions@key-partition-000000.iam.gserviceaccount.com.

Clicking on the “Create” button brings up the roles configuration stage. We need two roles:

Clicking on the “Create” button brings up the roles configuration stage. We need two roles:

  • Cloud Run Admin, the role which will allow us to create a new Cloud Run deployment;
  • Storage Admin, the role which allow us to upload our Docker images to the GCP’s Container Registry.
  • Service Account User, the role that allows the service account to act as a user.

The “Continue” button takes us to the third and final step: JSON key creation. To do that, just click the “Create Key” button, choose “JSON” > “Create.” A file will be downloaded, referred to as key-partition-000000–0123456789ab.json. Keep it, we’ll use it later.

API activation

Now that we have a user with the proper permissions, we need to activate the two services we need.

To enable the container registry, choose the “Container Registry” (obvious, right?) menu of the GCP interface, and click the “Enable Container Registry API” button… and we’re done.

To enable Cloud Run, choose the “Cloud Run” (obvious again, right?) menu from the GCP interface and click “Start Using Cloud Run.” Again, we’re done.

Project Creation

To initiate the project, let’s create a GitHub repository and add several files:

  • A simple welcome <h1>Hello World!</h1> HTML page;
  • A simple error <h1>Server error</h1> HTML page;
  • A nginx/default.conf file that will serve as the welcome file
  • A Dockerfile to build Docker images;
  • .github/workflows/googlecloudrun.yml file to configure the continuous deployment.

nginx configuration

server {
listen 8080;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
view raw default.conf hosted with ❤ by GitHub

Cloud Run listens on localhost:8080. So, the first step is to set these values with server_name and listen.

Then, we configure the file location that nginx should serve; the root key is where all the static files to serve are located. In this example, that’s in usr/share/nginx/html. The index key declares to nginx what root files to resolve; here, index.html or index.htm will be resolved.

Finally, we forward the 50X errors to a special file with the command error_page and handle the mapping in nginxwith location to our error HTML file.

Dockerfile

FROM nginx:alpine
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
COPY html /usr/share/nginx/html
view raw Dockerfile hosted with ❤ by GitHub

Since our application is really simple, we only have three steps in our Docker file:

  • We choose the alpine nginx image to ensure we have the lightest bundle possible;
  • We copy the nginx conf in its target folder;
  • Finally, we copy our HTML files in the public folder of our newly-created container.

GitHub actions YAML

name: Deploy the application to Google Cloud Run
on:
push:
branches:
- 'master'
jobs:
deploy:
name: Deploy job
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v1
- name: Build Docker image
run: |
docker build . --tag eu.gcr.io/${{ secrets.GCLOUD_PROJECT }}/${{ secrets.GCLOUD_APP_NAME }}
- name: Authenticate into Google Cloud Platform
uses: GoogleCloudPlatform/github-actions/setup-gcloud@master
with:
service_account_email: ${{ secrets.GCLOUD_EMAIL }}
service_account_key: ${{ secrets.GCLOUD_AUTH }}
- name: Configure Docker to use Google Cloud Platform
run: "gcloud auth configure-docker --quiet"
- name: Push image to Google Cloud Container Registry
run: docker push eu.gcr.io/${{ secrets.GCLOUD_PROJECT }}/${{ secrets.GCLOUD_APP_NAME }}
- name: Install beta commands and deploy on cloud run
run: |
gcloud components install beta --quiet
gcloud beta run deploy ${{ secrets.GCLOUD_APP_NAME }} --quiet --image eu.gcr.io/${{ secrets.GCLOUD_PROJECT }}/${{ secrets.GCLOUD_APP_NAME }} --project ${{ secrets.GCLOUD_PROJECT }} --region europe-west1 --platform managed
view raw googlecloudrun.yml hosted with ❤ by GitHub

This is probably the most complicated part of the project, although you’ll see that it’s still pretty simple.

Our project will make the deploy on every commit made on the master branch. We will, however, ignore everything made on the other branches since it’s a deployment script. If you need to go further, you may want to make a deploy on a different endpoint for every other branch, but that’s not today’s topic.

Our CD also has only one job, since we have a pretty simple application -simple nginx server with static files. But in real life, you may want other steps to build your application, or to run the tests, for example. We choose to run the build on ubuntu-latest, but other possibilities are available.

The job will be composed of several steps. But before digging into them, let’s see what actions are required:

  • actions/checkout@v1 to pull the GitHub project sources we are going to deploy;
  • actions/gcloud/cli@master to perform every step related to GCP.

Now let’s dive into the steps. There are six of them:

The first one is to ensure you get the application sources from GitHub. Because Actions might be triggered on issue creation, for example, in which we probably don’t need to get the sources. GitHub gives only the mandatory data by default.

The second one uses the Docker to build our image. Moreover, since we are uploading the image to Google Cloud Container Registry, we have to tag it with the name of the target image URI. The URI is built using:

  • A host: eu.gcr.io;
  • A project: secrets.GCLOUD_PROJECT, a secret we will talk about in a moment;
  • An app name: secrets.GCLOUD_APP_NAME; also a secret.

The third step is the beginning of GCP-related commands. We first need to authenticate ourselves with the secret key GCLOUD_AUTH via the auth action.

The fourth step links GCP and Docker together so it knows where to push images.

The fifth step consists of uploading our image on the container registry. This one has a specificity: we need to use both Docker and GCloud in that command, so choose the GCloud action, and use the sh entrypoint in order to have both thegcloud and docker commands available, to push the image. Thanks to the fourth step, Docker will push images in the right place.

The final step is actually two commands. Here, the result of the first is not available in the second if we split them into two different steps. The two commands are responsible for:

  • Installing the beta components, since Google Cloud Run is still in beta;
  • Deploying the app into Cloud Run. You will need to set the region you prefer and the platform (managed or GKE).

Declaring GitHub secrets

Secrets interface for GitHub Actions Secrets interface for GitHub Actions

In our YAML file, we reference three secret keys:

  • GCLOUD_APP_NAME: the name of the application on Cloud Run. In this example, it will be cloud-run-github-actions.
  • GCLOUD_PROJECT: the project ID on Google Cloud Platform. You can find it on the “Project Selection” popup.
  • GCLOUD_EMAIL : the service account email we create earlier that looks like github-actions@key-partition-000000.iam.gserviceaccount.com.
  • GCLOUD_AUTH: the base 64-encoded content of the JSON file we downloaded at the beginning of the story, key-partition-000000–0123456789ab.json.

And we’re ready to execute our deployment from GitHub Actions!

Conclusion

We can now deploy our project continuously and easily on Google Cloud Run. Our example is pretty simple, but since we’re using a Docker image, we’re free to have any kind of application we want. Just note that the application must listen to the PORT environment variable: Cloud Run will only listen to an app on that port!

You can find the sources on Github.