Day-to-day, I mainly contribute to Elva, a powerhouse of AWS experts aiming to help our customers elevate their cloud journeys using serverless.
However, sometimes life brings you into other platforms like Google Cloud in this case (which I also like a lot). This time, I wanted to set up container deploys as a part of a GitHub Action workflow to push images to Google Cloud Artifact Registry.
In the past, we primarily established our service-to-service (GitHub-to-Google-Cloud) authentication using service accounts with key files. While service account keys are powerful tools, these long-lived keys can present security challenges if not handled with care. The modern way of doing this is using Workload Identity Federation, which makes it easier and more secure to use Identity and Access Management (IAM) to grant external identities the necessary IAM roles and rights.
Let's look at how to set up the workflow file and the necessary Google Cloud configurations to achieve this.
Step 0: Set Up Workload Identity Federation in Google Cloud
Before we even touch the GitHub Actions YAML, we need to configure Google Cloud to trust our GitHub Actions workflow. This will be a secure way to allow our GitHub Actions to access Google Cloud resources without needing to manage long-lived service account keys.
Here's a script that sets up everything you need.
Important:
- Make sure you have the
gcloud
CLI installed and configured.- Otherwise you can install:
brew install google-cloud-sdk
- Otherwise you can install:
- You'll need to replace placeholder values in the script.
#!/bin/bash
# 1. Setup
# --- Input Variables (UPDATE THESE VALUES!) ---
GCLOUD_PROJECT="your-gcp-project-id"
GITHUB_REPO="your-github-username/your-github-repo-name"
# --- Optional ---
# Name for the new service account, identity pool and identity provider, change if you want/need
GCLOUD_SERVICE_ACCOUNT="github-deployer-account"
GCLOUD_IDENTITY_POOL="github-deployer-auth-pool"
GCLOUD_IDENTITY_PROVIDER="github-deployer-auth-provider"
GCLOUD_SERVICE_ACCOUNT_EMAIL="${GCLOUD_SERVICE_ACCOUNT}@${GCLOUD_PROJECT}.iam.gserviceaccount.com"
echo "Starting Workload Identity Federation setup for \
project: ${GCLOUD_PROJECT} and repo: ${GITHUB_REPO}"
# 2. Enable the IAM Credentials API
gcloud services enable iamcredentials.googleapis.com \
--project "${GCLOUD_PROJECT}"
if [ $? -ne 0 ]; then echo "Error enabling IAM Credentials API. Exiting."; exit 1; fi
# 3. Create a new service account
gcloud iam service-accounts create ${GCLOUD_SERVICE_ACCOUNT} \
--project "${GCLOUD_PROJECT}" \
--display-name="GitHub Actions Deployer Account for ${GITHUB_REPO}"
# 4. Grant the service account permission to write to Artifact Registry
gcloud projects add-iam-policy-binding "${GCLOUD_PROJECT}" \
--member="serviceAccount:${GCLOUD_SERVICE_ACCOUNT_EMAIL}" \
--role="roles/artifactregistry.writer"
if [ $? -ne 0 ]; then echo "Error granting Artifact Registry Writer role. Exiting."; exit 1; fi
# 5. Create a Workload Identity Pool
gcloud iam workload-identity-pools create ${GCLOUD_IDENTITY_POOL} \
--project="${GCLOUD_PROJECT}" \
--location="global" \
--display-name="GitHub Actions Auth Pool"
# 6. Get the full ID of the Workload Identity Pool
GCLOUD_WORKLOAD_IDENTITY_POOL_ID=$(gcloud iam workload-identity-pools describe ${GCLOUD_IDENTITY_POOL} \
--project="${GCLOUD_PROJECT}" \
--location="global" \
--format="value(name)")
if [ -z "${GCLOUD_WORKLOAD_IDENTITY_POOL_ID}" ]; then echo "Error fetching Workload Identity Pool ID. Exiting."; exit 1; fi
# 7. Create an OIDC Workload Identity Provider in the pool..."
gcloud iam workload-identity-pools providers create-oidc ${GCLOUD_IDENTITY_PROVIDER} \
--project="${GCLOUD_PROJECT}" \
--location="global" \
--workload-identity-pool="${GCLOUD_IDENTITY_POOL}" \
--display-name="GitHub Actions Auth Provider" \
--attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository" \
--issuer-uri="https://token.actions.githubusercontent.com"
# 8. Allow authentications from the GitHub OIDC provider to impersonate the Google Cloud service account
# This binds the GitHub repo to the service account via the WIF pool and provider.
gcloud iam service-accounts add-iam-policy-binding "${GCLOUD_SERVICE_ACCOUNT_EMAIL}" \
--project="${GCLOUD_PROJECT}" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/${GCLOUD_WORKLOAD_IDENTITY_POOL_ID}/attribute.repository/${GITHUB_REPO}"
if [ $? -ne 0 ]; then echo "Error binding service account for impersonation. Exiting."; exit 1; fi
# 9. Get the full ID of the Workload Identity Provider (needed for GitHub Secrets)
GCP_WORKLOAD_IDENTITY_PROVIDER_ID=$(gcloud iam workload-identity-pools providers describe ${GCLOUD_IDENTITY_PROVIDER} \
--project="${GCLOUD_PROJECT}" \
--location="global" \
--workload-identity-pool="${GCLOUD_IDENTITY_POOL}" \
--format="value(name)")
if [ -z "${GCP_WORKLOAD_IDENTITY_PROVIDER_ID}" ]; then echo "Error fetching Workload Identity Provider ID. Exiting."; exit 1; fi
echo ""
echo "--- GitHub Secrets ---"
echo "Add these to your GitHub repository secrets:"
echo "GCP_PROJECT_ID: ${GCLOUD_PROJECT}"
echo "GCP_WORKLOAD_IDENTITY_PROVIDER_ID: ${GCP_WORKLOAD_IDENTITY_PROVIDER_ID}"
echo "GCP_SERVICE_ACCOUNT_EMAIL: ${GCLOUD_SERVICE_ACCOUNT_EMAIL}"
echo ""
echo "Setup complete!"
What this script does:
- Setup
-
GCLOUD_PROJECT
: You must set this to your Google Cloud Project ID. -
GITHUB_REPO
: You must set this to your GitHub repository in the formatusername/repository-name
(e.g.,my-cool-user/my-awesome-app
).
-
- Enables IAM Credentials API
- This API is necessary for your GitHub Action to mint short-lived credentials for the service account.
- Creates a Service Account
- This is the Google Cloud identity that your GitHub Action will impersonate. It's like a robot user for your automation.
- Grants Artifact Registry Permissions
- This gives the newly created service account permission to push images to Google Cloud Artifact Registry.
- Creates a Workload Identity Pool
- This pool is a container for identity providers. It helps organize how external identities (like those from GitHub) are managed.
- Gets the Workload Identity Pool ID
- This retrieves the unique identifier for the pool, which is needed in later steps.
- Creates an OIDC Workload Identity Provider
- This is the key part. It tells Google Cloud to trust OIDC tokens issued by GitHub Actions (
https://token.actions.githubusercontent.com
). - The attribute-mapping tells Google Cloud how to map information from the GitHub OIDC token (like the repository name) to attributes Google Cloud can understand. This allows you to restrict which GitHub repositories can use this identity provider.
- This is the key part. It tells Google Cloud to trust OIDC tokens issued by GitHub Actions (
- Allows GitHub to Impersonate the Service Account
- This crucial step links everything together. It says that principals (in this case, your GitHub Action running in the specified
GITHUB_REPO
) that authenticate through the OIDC provider are allowed to impersonate the service account we created. TheprincipalSet
specifically targets your repository.
- This crucial step links everything together. It says that principals (in this case, your GitHub Action running in the specified
- Outputs IDs for GitHub Secrets
- The script will print out the
GCP_PROJECT_ID
,GCP_WORKLOAD_IDENTITY_PROVIDER_ID
(this will be the full path likeprojects/your-gcp-project-id/locations/global/workloadIdentityPools/github-deployer-auth-pool/providers/github-deployer-auth-provider
), andGCP_SERVICE_ACCOUNT_EMAIL
. You must add these as secrets in your GitHub repository settings so the Action workflow can use them. You do this by going to your Github repo, thenSettings > Secrets and variables > Actions > New repository secret
.
- The script will print out the
After running this script successfully and setting up the GitHub secrets, Google Cloud will be ready for your GitHub Action.
Now, let's look at the GitHub Actions workflow YAML.
name: Build Container
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
# Permissions are important for the worklaod identity federation to work
permissions:
contents: 'read'
id-token: 'write'
steps:
# ... our steps will go here
This initial block sets up the basic triggers (when the workflow runs) and the environment (Ubuntu latest) for our job.
It will run when we push to the main branch or when a pull request targets the main branch. The job, which we've called build, will run on the latest Ubuntu environment.
Notice the permissions block. We need contents: 'read' to allow the action to read your repository's content (which actions/checkout
does). More importantly, id-token: 'write'
is crucial. This permission allows the GitHub Actions runner to request an OIDC (OpenID Connect) token, which we'll use to securely authenticate with Google Cloud, thanks to the Workload Identity Federation setup we just did.
Now for the actual steps involved in the job.
Step 1: Checkout Code And Set Up Docker
First things first, we need to get our code and prepare Docker for building our image.
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
This initial part combines two actions:
-
actions/checkout@v4
: This action checks out your repository. -
docker/setup-buildx-action@v3
: This action installs and configures Docker.
Step 2: Authenticate to Google Cloud
To push our Docker image to Google Cloud Artifact Registry, our GitHub Action needs to authenticate with Google Cloud.
This step uses the Workload Identity Federation we configured in Step 0.
- uses: 'google-github-actions/auth@v2'
id: auth
with:
token_format: access_token
project_id: ${{ secrets.GCP_PROJECT_ID }}
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER_ID }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT_EMAIL }}
Let's break down the parameters:
-
auth
: We give this step an ID, auth. This is useful because this action outputs an access token that we can use in subsequent steps. -
token_format
: access_token: We're asking for an OAuth2 access token. -
project_id
: Your Google Cloud Project ID. This comes from theGCP_PROJECT_ID
secret you created from the script's output. -
workload_identity_provider
: The full identifier of your Workload Identity Provider. This comes from theWORKLOAD_IDENTITY_PROVIDER_ID
secret. -
service_account
: The email address of the Google Cloud service account that GitHub Actions will impersonate. This comes from theGCP_SERVICE_ACCOUNT_EMAIL
secret.
Step 3: Log in to Google Cloud Artifact Registry
Now that we have an access token from Google Cloud, we can use it to log in to Artifact Registry.
Artifact Registry uses standard Docker authentication mechanisms.
- uses: docker/login-action@v3
with:
registry: europe-docker.pkg.dev
username: oauth2accesstoken
password: '${{ steps.auth.outputs.access_token }}'
Let's break down the parameters:
-
registry
: This is the hostname of the Artifact Registry you want to push to. For example, if your images are in the europe region, it would beeurope-docker.pkg.dev
. Adjust this based on your registry's region. -
username
: When using an OAuth2 access token with Google Artifact Registry, the username is alwaysoauth2accesstoken
. -
password
: This is where we use the access token obtained in the previous step.${{ steps.auth.outputs.access_token }}
refers to the output namedaccess_token
from the step with the IDauth
.
Step 4: Build and Push Docker Image
Finally, we get to the part where we build our Docker image and push it.
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.ref == 'refs/heads/main' && github.event_name != 'pull_request' }}
tags: europe-docker.pkg.dev/${{secrets.GCP_PROJECT_ID }}/images/my-container-name:latest
Let's break down the parameters:
-
platforms: linux/amd64,linux/arm64
: This is an example of building for multiple platforms. Buildx makes this easy. You can adjust this to the platforms you need. -
push: ${{ github.ref == 'refs/heads/main' && github.event_name != 'pull_request' }}
: This is a crucial part for controlling when the image is actually pushed. We only want to push if the current Git reference isrefs/heads/main
(i.e., we're on the main branch) AND the event that triggered the workflow is not a pull_request. This prevents pushing images for every commit on a pull request branch, but still builds them (which can be useful for testing). -
tags
: This specifies the tag for your Docker image.
Optimizing Builds with Caching
Docker builds can sometimes be slow, especially if you have a large image or many dependencies. Caching can significantly speed up this process by reusing layers from previous builds. The docker/build-push-action
supports caching with GitHub Actions cache.
Let's update our "Build and push Docker image" step to include caching:
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
# ... add these lines
cache-from: type=gha
cache-to: type=gha,mode=max
Here's what the new lines mean:
-
cache-from: type=gha
: This tells the action to attempt to pull cache layers from the GitHub Actions cache. -
cache-to: type=gha,mode=max
: This tells the action to save the build cache to the GitHub Actions cache.mode=max
means it will include all layers, which provides the best potential for cache hits on subsequent runs, though it might make the cache slightly larger.
Using type=gha
is convenient because it uses the built-in GitHub Actions caching mechanism.
Wrapping Up: The Complete Workflow
So, putting it all together, here's our final YAML for the GitHub Action:
name: Build Container
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: 'read'
id-token: 'write'
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: 'google-github-actions/auth@v2'
id: auth
with:
token_format: access_token
project_id: ${{secrets.GCP_PROJECT_ID }}
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER_ID }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT_EMAIL }}
- uses: docker/login-action@v3
with:
registry: europe-docker.pkg.dev
username: oauth2accesstoken
password: '${{ steps.auth.outputs.access_token }}'
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.ref == 'refs/heads/main' && github.event_name != 'pull_request' }}
tags: europe-docker.pkg.dev/${{secrets.GCP_PROJECT_ID }}/images/my-container-name:latest
cache-from: type=gha
cache-to: type=gha,mode=max
And there you have it!
First, ensure you've run the setup script (Step 0) in your Google Cloud environment and configured the necessary GitHub secrets.
Then, this workflow will check out your code, authenticate to Google Cloud, log in to Artifact Registry, and then build and push your Docker image, complete with caching to speed things up.
Remember to replace placeholder values like europe-docker.pkg.dev
and my-container-name
with your own values.
You'll also need to ensure your Dockerfile is in the root of your repository (context: .
) or adjust the path accordingly.
This setup provides a secure and efficient way to manage your Docker image deployments to Google Cloud.
Top comments (0)