Pushing Multi-Architecture Container Images
Tags: cloudnative, docker, githubactions, azure
Published: 2023-09-07 17:57:03

---
series: Multi-Architecture Container Images
title: Pushing Multi-Architecture Container Images
description: Pushing Multi-Architecture Container Images to Azure Container Registry using GitHub Actions
published: true
date: 2023-09-07 13:00:00 UTC
tags: cloudnative, docker, githubactions, azure
canonical_url: https://paulyu.dev/article/pushing-multi-arch-container-images-guide/
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dnatojeakz36u9wa3fge.jpeg
---

## Introduction

My previous article, [Building Multi-Architecture Container Images](https://dev.to/azure/building-multi-architecture-container-images-ica), covered the basics of building multi-architecture container images using Docker Buildx. In this article, we'll explore how to push multi-architecture container images to Azure Container Registry (ACR) using GitHub Actions.

## What is a GitHub Action?

GitHub Actions is a continuous integration and continuous deployment (CI/CD) platform built into GitHub. It allows you to automate, customize, and execute your software development workflows. Using GitHub Actions, you can create workflows that respond to GitHub events, such as push, issue creation, or a new release.

## Workload identity federation for GitHub Actions

ACR is a private container registry in Azure. In order to push container images to ACR, you need to authorization to push into it. 

There are a few ways to authenticate to ACR. One way is to enable admin user access on ACR and use the admin username and password; this is like a superuser account that can do anything within ACR. Another way is to use a service principal that has the `AcrPush` Azure role assigned to it; this is a more granular approach that only allows the service principal to push container images to ACR. 

Using the service principal approach is the preferred method, but we need to consider the two ways it can authenticate to Azure. One way is to create a client secret for the service principal and store the credentials in [GitHub Secrets](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions). The other way is to use a [workload identity](https://learn.microsoft.com/azure/active-directory/workload-identities/workload-identities-overview) in Azure and federate it with GitHub Actions. 

[Workload identity federation](https://learn.microsoft.com/azure/active-directory/workload-identities/workload-identity-federation) is a relatively newer approach to authenticating to Azure and is the preferred method as it enables you to go "passwordless" and not store any passwords in GitHub Secrets 🔐 

I've written a [blog post](https://dev.to/azure/does-workload-identity-on-aks-work-across-tenants-4m31) that covers how to use workload identity to authenticate to Azure. So check that out for more info.

### Create a user-assigned managed identity

In order to implement workload identity federation for GitHub Actions, we need to create a user-assigned managed identity in Azure.

> We'll continue to use the [`osinfo`](https://github.com/pauldotyu/osinfo) repository from my previous article. If you haven't already gone through it, head back to the [previous article](https://dev.to/azure/building-multi-architecture-container-images-ica) and follow the steps to create the Azure resources and fork/clone the repository.

Run the following commands to create a new user-assigned managed identity in Azure and assign the `AcrPush` roles to the managed identity:

```bash
# Set the resource group name
RG_NAME=rg-multiarch

# Set the managed identity name
MANAGED_IDENTITY_NAME=mi-multiarch

# Create the managed identity and return the service principal object id
MANAGED_IDENTITY_OBJECT_ID=$(az identity create \
  --resource-group $RG_NAME \
  --name $MANAGED_IDENTITY_NAME \
  --query principalId -o tsv)

# Get the ACR resource id
ACR_RESOURCE_ID=$(az acr list --resource-group $RG_NAME --query "[0].id" -o tsv)

# Get the ACR name
ACR_NAME=$(az acr list --resource-group $RG_NAME --query "[0].name" -o tsv)

# Grant the managed identity access to ACR resource
az role assignment create \
  --role "AcrPush" \
  --assignee-object-id $MANAGED_IDENTITY_OBJECT_ID \
  --assignee-principal-type ServicePrincipal \
  --scope $ACR_RESOURCE_ID
```

You could also use an Azure Application Registration with a Service Principal but I prefer to use a user-assigned managed identity because it ends up being a resource in Azure that you can manage and delete. Some organizations have policies that prevent the creation of Azure Application Registrations, so using a user-assigned managed identity is a good alternative.

### Establish a trust relationship between Azure and GitHub Actions

Next, we need to create the Federated Identity Credential in Azure to federate the managed identity with GitHub Actions.

```bash
# Set the federated credential name
FEDERATED_CRED_NAME=fc-multiarch

# Set the GitHub repository name in the format: pauldotyu/osinfo
GH_REPO=<YOUR_GITHUB_ORG_AND_REPO_NAME>

# Set the GitHub branch name
GH_BRANCH=main

# Create the federated credential
az identity federated-credential create \
  --name ${FEDERATED_CRED_NAME} \
  --identity-name ${MANAGED_IDENTITY_NAME} \
  --resource-group ${RG_NAME} \
  --issuer https://token.actions.githubusercontent.com \
  --subject repo:${GH_REPO}:ref:refs/heads/${GH_BRANCH}
```

Here, I set `GH_REPO` to `pauldotyu/osinfo` so the subject will point to the `pauldotyu/osinfo` repository on the `main` branch which is what Azure AD will expect when negotiating the incoming token from GitHub. You can set the subject to expect a token from Environments, Pull Requests, or Tags as well.

The `issuer` is the URL that GitHub Actions uses to issue the token and where Azure will send the token to be validated. 

For more information on how to configure OpenID Connect in Azure, see [Set up Azure Login with OpenID Connect authentication](https://learn.microsoft.com/azure/developer/github/connect-from-azure?tabs=azure-cli%2Clinux#set-up-azure-login-with-openid-connect-authentication).

### Prepare the GitHub repository

Last thing we need to do is set some GitHub Secrets for the repository. We'll need to set the `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, and `AZURE_SUBSCRIPTION_ID` secrets. We'll use the GitHub CLI to set the secrets. These aren't passwords, but you should treat them as sensitive information and not add them to your repository code. 

> If you do not have the GitHub CLI installed, you can find the installation instructions [here](https://cli.github.com/).

Make sure you fork and clone the `osinfo` repository from my previous article, open a terminal at the root of the repository, then run the following commands:

```bash
# Get the client id and set the secret
gh secret set AZURE_CLIENT_ID -b $(az identity show \
  --resource-group $RG_NAME \
  --name $MANAGED_IDENTITY_NAME \
  --query clientId -o tsv)
``
# Get the tenant id and set the secret
gh secret set AZURE_TENANT_ID -b $(az identity show \
  --resource-group $RG_NAME \
  --name $MANAGED_IDENTITY_NAME \
  --query tenantId -o tsv)

# Get the subscription id and set the secret
gh secret set AZURE_SUBSCRIPTION_ID -b $(az account show \
  --query id -o tsv)

# Get the ACR login server and set the secret
gh secret set ACR_LOGIN_SERVER -b $(az acr show \
  --resource-group $RG_NAME \
  --name $ACR_NAME \
  --query loginServer -o tsv)
```

We're now ready to build and push our container image to ACR using GitHub Actions.

## Building the GitHub Action Workflow

### Overview Workflow

When building out automation workflows, I like to jot down the high-level steps of what the workflow will do. This helps me visualize the workflow and identify any potential issues.

Here's what we want to accomplish:

1. Log into Azure
2. Log into Azure Container Registry
3. Build the container image using Docker Buildx
4. Push the container image to Azure Container Registry

There are some GitHub Actions available in the [GitHub Marketplace](https://github.com/marketplace?type=actions) that can help us accomplish these tasks. Here's what I will use:

- [Azure Login](https://github.com/marketplace/actions/azure-login)
- [Docker Buildx](https://github.com/marketplace/actions/docker-setup-buildx)
- [Docker Build and Push](https://github.com/marketplace/actions/build-and-push-docker-images)

### The workflow File

GitHub Action workflows are defined in a YAML file and must be stored in the `.github/workflows` directory in the root of the repository.

In your terminal, run the following commands to create a new file in the `.github/workflows` directory called `container-image.yml`:

```bash
mkdir -p .github/workflows
touch .github/workflows/container-image.yml
```

Open the workflow file using VSCode so that we can start adding code to it. First thing we need to do is give it a name. We'll call it `container-image`, same as the file name.

Add the following to the top of the file:

```yaml
name: container-image
```

You need to specify when the workflow should run. We'll use the `push` event to trigger the workflow when a commit is pushed to the `main` branch. Add the following to the workflow file:

```yaml
on:
  push:
    branches:
      - 'main'
```

Here's the **critical** part when using federated identity credentials. We need to add permissions to the workflow to allow it to write `id-token` and read `contents`. This is required for the workflow to be able to authenticate to Azure. Add the following to the workflow file:

```yaml
permissions:
  id-token: write
  contents: read
```

Now we setup a single job called `build-and-push` that runs on `ubuntu-latest` which is the type of machine the workflow will run on. This job will contain all the steps to build and push the container image to ACR. We'll use the `steps` keyword to define the steps. Add the following to the workflow file:

```yaml
jobs:
  build-and-push:
    runs-on: ubuntu-latest
```

Directly underneath `runs-on`, we add steps for the workflow to run. These steps will be run serially. First step is to checkout the repository code. We'll use the `actions/checkout` GitHub Action to checkout the repository code. Add the following to the workflow file:

```yaml
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
```

### Azure Login

Next step is to login to Azure using the federated identity credential we created earlier. We will use the `azure/login` GitHub Action to login, but notice we're referencing the `client-id`, `tenant-id`, and `subscription-id` using GitHub secrets we created earlier for the repository. Add the following to the workflow file:

```yaml
      - name: Login to Azure
        uses: azure/login@v1
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
```

### ACR Login

Using our authenticated session, we can login to ACR. We'll use a simple `az acr login` command here. Notice here, we're referencing the `ACR_LOGIN_SERVER` secret we created earlier for the repository. Add the following to the workflow file:

```yaml
      - name: Login to Azure Container Registry
        run: az acr login --name ${{ secrets.ACR_LOGIN_SERVER }}
```

### Setup Docker Buildx

Now we need to setup Docker Buildx. Remember from my previous article, we need to use Docker Buildx to build multi-architecture container images. We'll use the `docker/setup-buildx-action` GitHub Action to set it up. Add the following to the workflow file:

```yaml
      - name: Setup Docker Buildx
        uses: docker/setup-buildx-action@v2
```

### Docker Build and Push to ACR

Finally, we'll use the `docker/build-push-action` GitHub Action to build and push the container image to ACR.

```yaml
      - name: Build image and push to registry
        uses: docker/build-push-action@v2
        with:
          context: .
          file: Dockerfile
          platforms: linux/amd64,linux/arm64
          push: true
          tags: |
            ${{ secrets.ACR_LOGIN_SERVER }}/osinfo:${{ github.sha }}
```

Here, we're building the container image using the `Dockerfile` in the root of the repository. The container image will be built for the `linux/amd64` and `linux/arm64` platforms and tagged using the `github.sha` which is the commit SHA of the commit that triggered the workflow.


### The Final Workflow File

To see what your full workflow file should look like by now, you can reference the [container-image.yml](https://github.com/pauldotyu/osinfo/blob/gh-action/.github/workflows/container-image.yml) in my repo.

With the workflow file complete, we can commit the changes to the repository and push it to GitHub.

Finally, head over to the Actions tab in your repository, you should see the workflow running 🚀

## Conclusion

In this article, we explored how to push multi-architecture container images to Azure Container Registry using GitHub Actions. We used workload identity federation to authenticate to Azure and used GitHub Actions to automate the building and pushing of our multi-architecture container images to ACR.

This was a very manual process to setup, but once it's setup, it's a set it and forget it type of thing. You can now push multi-architecture container images to ACR each time you make a change to your code! 

When learning something new, I like to do it manually first so I can understand how it works. Then I'll lean on tools to automate the process. If you haven't checked out [Draft](https://github.com/Azure/draft) which is an open-source tool that helps you automate the scaffolding of cloud-native applications for deploying to Kubernetes, you should check it out! This tool also includes the ability to create GitHub Actions workflows using... you guessed it, workload identity federation! 😎 

For a more in-depth look at how to use Draft, check out my teammate [@StevenMurawski](https://twitter.com/StevenMurawski)'s [blog post](https://aka.ms/cloudnative/draftdeepdive).

Next up, we'll add to our GitHub Action workflow to include steps to secure our container images using open-source tools as outlined by my other teammate, [@joshduffney](https://twitter.com/joshduffney). You should checkout his [blog post](https://aka.ms/cloudnative/SecureSupplyChainTools) too.

If you have any feedback or suggestions, please feel free to reach out to me on [Twitter](https://twitter.com/pauldotyu) or [LinkedIn](https://www.linkedin.com/in/yupaul).

Peace ✌️

## References

- [GitHub Actions](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions)
- [Workload identity federation](https://learn.microsoft.com/azure/active-directory/workload-identities/workload-identity-federation)
- [Configuring OpenID Connect in Azure](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-azure)
- [Set up Azure Login with OpenID Connect authentication](https://learn.microsoft.com/azure/developer/github/connect-from-azure?tabs=azure-cli%2Clinux#set-up-azure-login-with-openid-connect-authentication)