DevOps on Power

 View Only

Build multi-architecture container images on GitHub Actions using native nodes

By Yussuf Shaikh posted Fri June 16, 2023 12:08 AM



Last update: June 30, 2023

There are already a few good blogs[1][2][3] available in this group that demonstrates how to build multi-arch container images on GitHub Actions using Buildx. However, they are using QEMU which is a free and open-source emulator for running cross-builds.

Using QEMU presents its own problems where the main point is the slowness which cannot match when we run the builds natively. Even there is no guarantee that the build will always succeed when we use low-level code in the project. These pain points forced us to use native nodes as part of the same Buildx workflow inside a GitHub Action.

If you as well want to use native nodes to build your projects on multiple architectures including ppc64le then this article is for you.


Let's consider we want to build our code and create container images for amd64 and ppc64le architectures.

First of all, we will need a machine for native builds running on a public network so that Buildx running on GitHub Action Runner can access it. IBM Cloud Power Virtual Server is a good choice for running a ppc64le machine. It could have any operating system with docker installed and SSH enabled.


Let's now write a workflow to use the native node for building the ppc64le architecture image.  We will use the default docker context for amd64 architecture which is GitHub Action runner itself.

name: Build and Publish
    branches: [ "main" ]
    # Allows you to run this workflow manually from the Actions tab
        description: 'Tag to attach to image'
        required: true
    name: Publish container image
      REGISTRY:${{ secrets.QUAY_USER }}
      REPO_NAME: ${{ }}
    runs-on: 'ubuntu-latest'
      - name: Check out the repo
        uses: actions/checkout@v3
      - name: Install SSH key
        uses: shimataro/ssh-key-action@v2
          key: ${{ secrets.BUILDER_PPC64LE_SSH_KEY }}
          name: id_rsa_ppc64le
          known_hosts: unnecessary
          config: |
            Host builder-ppc64le
              HostName ${{ secrets.BUILDER_PPC64LE_SSH_HOST }}
              User root
              IdentityFile ~/.ssh/id_rsa_ppc64le
              StrictHostKeyChecking no
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
          version: v0.11.0
          platforms: linux/amd64
          append: |
            - endpoint: ssh://builder-ppc64le
              platforms: linux/ppc64le
      - name: Login to
        uses: docker/login-action@v1
          username: ${{ secrets.QUAY_USER }}
          password: ${{ secrets.QUAY_TOKEN }}
      - name: Build and push
        id: docker_build
        uses: docker/build-push-action@v4
          platforms: linux/amd64,linux/ppc64le
          push: true
          tags: ${{ env.REGISTRY }}/${{ env.REPO_NAME }}:${{ github.event.inputs.tag || 'unstable' }}

Also, we will need a simple Dockerfile for testing.

FROM ubuntu

RUN echo "Image created for $(uname -m)" > message.txt

CMD ["cat","message.txt"]


If you are unfamiliar with GitHub Action workflow then let me explain each step written above with a few notes.

1. Check out the repo

It does nothing but checks-out the GitHub repository and correct ref/SHA into the workflow.

2. Install SSH key

This will set up password-less access to the remote ppc64le node (builder-ppc64le). It creates a key named id_rsa_ppc64le with the given configurations under config. Note the secrets[4] used in this step:

BUILDER_PPC64LE_SSH_KEY value is the private SSH key to connect to the remote node.
BUILDER_PPC64LE_SSH_HOST value is the host address ie. The IP address of the remote node.
3. Set up Docker Buildx

This is the main step in configuring the remote node (builder-ppc64le) in the Buildx context list for ppc64le and using the default context for amd64. Note: I have purposely used version "v0.11.0" which has a fix for mixed OCI types. You may remove this line to use the latest Buildx version.

4. Login to

For pushing the multi-arch images to a registry we are using and we will need to authenticate. Note that we are using secrets[4] here.

5. Build and push

This step will build the images for both architectures and also push them to the registry including the multi-arch manifests. Note that the image name is constructed using the environment variables set in the env section and the tag is user input or 'unstable' by default.


Once we have set up the GitHub Action workflow for our project it will create the multi-arch images using the native node. We can verify that by login into the remote node and checking for any running containers triggered during the workflow execution.

# docker ps
CONTAINER ID  IMAGE                          COMMAND      CREATED         STATUS        PORTS    NAMES
47b4f6d4fda5  moby/buildkit:buildx-stable-1  "buildkitd"  10 seconds ago  Up 9 seconds           buildx_buildkit_builder-be1d4350-ba90-4058-8d3e-cf72409663bc1

Here is a screenshot of the workflow execution building the multi-architecture images.

Learn more about configuring your Buildx builder and adding additional nodes here.

Note: You may face a 'context deadline exceeded' error while adding the ppc64le builder in the "Set up Docker Buildx" step above. This is because of the long SSH connection time due to some dropped network packets. To fix this issue you need to set the public network MTU to 1450 on the native ppc64le node.


Once the images are pushed to the registry we can test them out by pulling into a VM for each architecture.

For ppc64le:
# uname -m
# docker run --rm <multi-arch image repo/name>:unstable
Image created for ppc64le
For amd64:
% uname -m
% docker run --rm <multi-arch image repo/name>:unstable
Image created for x86_64


[1] - Build multi-arch images on GitHub Actions with Buildx

[2] - Building multi-arch container images with GitHub Actions and Buildah

[3] - Cross-compiling using GitHub Actions and QEMU

[4] - Encrypted secrets