Building and Pushing to Artifact Registry with Github Actions

Sarah Kapelner
5 min readSep 20, 2023

--

Photo by Richy Great on Unsplash

Github Actions is a continuous integration and continuous delivery (CI/CD) tool that triggers actions based on events in a Github repository e.g. a pull request. This article illustrates how to create a Github Actions workflow that builds a Docker image and pushes that image to Google Cloud Artifact Registry. In this article we will authenticate with a Service Account Key.

Creating and Configuring a Service Account

First ensure that you are authenticated through the gcloud account and project you intend to use.

#Authorize gcloud to use your Google Cloud credentials
gcloud auth login
gcloud auth list
gcloud project list

#Switch to a different account and/or project
gcloud config set account $ACCOUNT
gcloud config set project $MY_PROJECT_ID

In order to work with Google Cloud through Github Actions we will create a service account. A service account allows Github Actions to access Google Cloud Resources i.e. make calls to GCP APIs. A service account can be created with the following gcloud command:

gcloud iam service-accounts create githubactions \                                                              
--description="service acct for github actions" \
--display-name="Github Actions"

The next step is to grant the service account an IAM role:

gcloud iam service-accounts add-iam-policy-binding PROJECT_NUMBER-compute@developer.gserviceaccount.com \
--member="serviceAccount:githubactions@PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/iam.serviceAccountUser"

Next we need to grant some permissions to the service account which Github Actions can take advantage of by impersonating the service account. The Service Account Token Creator role allows the service account to create an OAuth 2.0 access token. Github Actions uses the access token to authenticate requests to GCP APIs. The Artifact Registry Writer role allows Github Actions to read and write artifacts i.e. pull/push images to and from Artifact Registry.

After granting permissions we need to add a Service Account Key. In the Google Cloud Console go to IAM > Service Accounts. Click on the three dots menu next to the service account you just created and click on Manage Keys. Add a JSON key.

Add the JSON key that you just downloaded to a Github Secret. In this example I named my Github Secret SERVICE_ACCOUNT_KEY.

Writing the Workflow

Github Actions workflows are defined as yaml files. Included in a workflow is one or more jobs or sets of steps (shell commands) we want the action to perform in response to an event in the repository. Each job runs in its own virtual machine (runner) and can be configured to run with other jobs sequentially or in parallel, however, in this article we will write a workflow with a single job.

In the following section of my workflow I give the workflow a name and specify the events I want to trigger the action. Then I specify some environmental variables including my project ID, project region and the location of my Artifact Registry repository.

name: Build and Push to Artifact Registry

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]

env:
PROJECT_ID: fake-app-323017
REGION: northamerica-northeast1
GAR_LOCATION: northamerica-northeast1-docker.pkg.dev/fake-app-323017/repo-1/

Next I specify the type of environment the runner will run the job on (ubuntu-latest) and checkout my Github repository with actions/checkout@v3. Note the “Checkout” step must be placed prior to the “auth” step (discussed in the next section) in order to avoid authentication problems.

job:
build-push-artifact:
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: "actions/checkout@v3"

This next section of the workflow includes three steps. In the “auth” step a credential file is created with my SERVICE_ACCOUNT_KEY and then that file is used in the “Set up Cloud SDK” step to authenticate my Google Cloud account. I then use gcloud info to optionally display information about my current gcloud environment.

      - id: "auth"
uses: "google-github-actions/auth@v1"
with:
credentials_json: "${{ secrets.SERVICE_ACCOUNT_KEY }}"

- name: "Set up Cloud SDK"
uses: "google-github-actions/setup-gcloud@v1"

- name: "Use gcloud CLI"
run: "gcloud info"

In the next section of the workflow I authenticate Docker to access the Artifact Registry by running the gcloud auth configure-docker command. I added the — quiet flag to swap any interactive prompts in favor of defaults.

      - name: "Docker auth"
run: |-
gcloud auth configure-docker ${{ env.REGION }}-docker.pkg.dev --quiet

At this point all we have left to add to the workflow are the steps to build and push the image. Replace WORKING_DIRECTORY with the top level directory of your Github repository and replace DOCKERFILE_LOCATION with the location of your Dockerfile relative to the top level directory. Tag your image using the location of your Artifact Registry Repository and push the image to that same location (GAR_LOCATION environmental variable in this example).

      - name: Build image
run: docker build . --file DOCKERFILE_LOCATION --tag ${{ env.GAR_LOCATION }}
working-directory: WORKING_DIRECTORY

- name: Push image
run: docker push ${{ env.GAR_LOCATION }}

The last step is to commit your workflow and push it to your Github repository. Below is the complete workflow.yml file:

name: Build and Push to Artifact Registry

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]

env:
PROJECT_ID: fake-app-323017
REGION: northamerica-northeast1
GAR_LOCATION: northamerica-northeast1-docker.pkg.dev/fake-app-323017/repo-1/

job:
build-push-artifact:
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: "actions/checkout@v3"

- id: "auth"
uses: "google-github-actions/auth@v1"
with:
credentials_json: "${{ secrets.SERVICE_ACCOUNT_KEY }}"

- name: "Set up Cloud SDK"
uses: "google-github-actions/setup-gcloud@v1"

- name: "Use gcloud CLI"
run: "gcloud info"

- name: "Docker auth"
run: |-
gcloud auth configure-docker ${{ env.REGION }}-docker.pkg.dev --quiet

- name: Build image
run: docker build . --file DOCKERFILE_LOCATION --tag ${{ env.GAR_LOCATION }}
working-directory: WORKING_DIRECTORY

- name: Push image
run: docker push ${{ env.GAR_LOCATION }}

--

--