How to push a Docker image to Google Artifact Registry with a GitHub Action without using a Service Account
This how I managed to push a Docker image in a Google Cloud Artifact Registry using a GitHub Actions workflow without using a GCP Service Account.
Before using this tutorial make sure you have :
- A GitHub repository containing a Dockerfile
- A GCP project with sufficient permissions to make changes to it
- The
gcloud
tool installed, authenticated and configured to use the GCP project. We will not use the--project
flag in the followinggcloud
commands.
Give the new Artifact Registry the name and location of your choice.
# Example
# DOCKER_REGISTRY_NAME = "my-registry"
# DOCKER_REGISTRY_LOCATION = "europe-west9"
gcloud artifacts repositories create "${DOCKER_REGISTRY_NAME}" \
--repository-format="docker" \
--location="${DOCKER_REGISTRY_LOCATION}" \
The following setup is derived from the documentation of the Google auth GitHub Action
First create a Workload Identity Pool named github
(feel free to change its name)
gcloud iam workload-identity-pools create "github" \
--location="global" \
--display-name="GitHub Actions Pool"
Using GitHub OIDC server, create a Workload Identity Pool Provider named my-gh-repo
(feel free to change its name) which includes a condition so that we are limiting what repository will be able to use it. Here, we're limiting to GitHub repositories owned by an user or an organization.
Note that we're also defining the mapping between attributes from authentication credentials issued by GitHub and Google Cloud attributes (--attribute-mapping
). We will use these attributes at the next step.
gcloud iam workload-identity-pools providers create-oidc "my-gh-repo" \
--location="global" \
--workload-identity-pool="github" \
--display-name="My GitHub repo Provider" \
--attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner" \
--attribute-condition="assertion.repository_owner == '${GH_REPO_OWNER}'" \
--issuer-uri="https://token.actions.githubusercontent.com"
Here we're giving the permission to GitHub repository owner_name/repository_name
to push into the artifact registry we created at the beginning.
Practically speaking we're filtering on the attribute.repository
attribute using a principalSet
expression.
Note that the GitHub repository name must include the owner (user or organization).
# Set the FULL GitHub repository name that will get the rights to push Docker images to Artifact registry
GH_REPO_NAME="owner_name/repository_name"
# Get full name of the Workload Identity Pool (e.g projects/123456789/locations/global/workloadIdentityPools/github)
WIP_NAME=$(gcloud iam workload-identity-pools describe "github" --format="value(name)" --location="global")
gcloud artifacts repositories add-iam-policy-binding ${DOCKER_REGISTRY_NAME} \
--role="roles/artifactregistry.writer" \
--member="principalSet://iam.googleapis.com/${WIP_NAME}/attribute.repository/${GH_REPO_NAME}" \
--location="${DOCKER_REGISTRY_LOCATION}"
For this step, we will need the full name of the Workload Identity Pool Provider. You can obtain it using the following command :
# This command will return a string of the following type projects/123456789/locations/global/workloadIdentityPools/github/providers/my-gh-repo
gcloud iam workload-identity-pools providers describe "my-gh-repo" \
--workload-identity-pool="github" \
--location="global" \
--format="value(name)"
Add a new YAML file in .github/workflows
inside your repository with the following minimal content.
You'll have to replace the following elements :
<GCP_PROJECT_NAME>
: The name of your GCP project<WIP_PROVIDER_FULL_NAME>
: The Workload Identity Pool Provider full name (e.gprojects/123456789/locations/global/workloadIdentityPools/github/providers/my-gh-repo
)<DOCKER_REGISTRY_LOCATION>
: The location of the Artifact Registry you create (e.geurope-west9
)<DOCKER_IMAGE_NAME>
: The name of the Docker image you want to push (e.gmyorg/myimage
)<DOCKER_IMAGE_TAG>
: The tag that will be set on the produced Docker image (e.glatest
)
Note that ${{ steps.auth.outputs.auth_token }}
will automatically be replaced by GitHub with the result of the Authenticate with Google Cloud
step.
name: "Push Docker image to GCP GAR"
on:
push:
jobs:
docker-release:
name: "Push Docker Image to Google Artifact Registry"
runs-on: ubuntu-latest
permissions:
contents: 'read'
id-token: 'write'
steps:
- id: checkout
name: Checkout
uses: actions/checkout@v2
- id: auth
name: Authenticate with Google Cloud
uses: google-github-actions/auth@v2
with:
project_id: <GCP_PROJECT_NAME>
workload_identity_provider: <WIP_PROVIDER_FULL_NAME>
- name: Login to Artifact Registry
uses: docker/login-action@v3
with:
registry: <DOCKER_REGISTRY_LOCATION>-docker.pkg.dev
username: oauth2accesstoken
password: ${{ steps.auth.outputs.auth_token }}
- id: docker-push-tagged
name: Tag Docker image and push to Google Artifact Registry
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
<DOCKER_REGISTRY_LOCATION>-docker.pkg.dev/<GCP_PROJECT_NAME>/<DOCKER_REGISTRY_NAME>/<DOCKER_IMAGE_NAME>:<DOCKER_IMAGE_TAG>
Commit and the workflow will build and push the Docker Image to GCP Artifact Registry.
It's still painfull but we're getting rid of a dedicated Service Account. Let me know if you identify some mistakes or improvements.