As you know, we have seen a huge shift in machines towards using ARM base CPUs like Apple Silicon to Snapdragon X from X86, and it's become essential to build Docker Images that support multiple architectures and run containers that are compatible and aligned with that architecture without facing any bottlenecks.
Using Docker Buildx we can very easily build multi-platform images. All builds executed via buildx
run with the Moby Buildkit builder engine. You can read more in detail here.
In the blog, we will leverage the functionality of Buildx to build a Multi-Arch Image, pushing it to DockerHub by automating all the tasks using a GitHub workflow/Actions.
Prerequisite
- A DockerHub account
- A good understanding of Docker
- A decent understanding of GitHub Actions
Getting started
Before starting, I assume you have already Dockerized your project, created a Dockerfile, and pushed it to GitHub. In case, you haven't done one yet and still want to try the process out, you can create a GitHub repo with just a minimal Dockerfile
in the root that prints "Hello World" by running an echo command using Alpine as the base image. Dockerfile syntax for it:
FROM alpine:3.20
CMD ["echo", "Hello World!"]
For the GitHub Workflow to have the privilege to push the Docker image to DockerHub we need to add a Docker username and Personal Access Token to GitHub Secrets. For that go into the repo settings then Secrets and Variables, and select the secret type Actions (as shown in the image below). Now create a secret name DOCKERHUB_USERNAME
and the secret value as your DockerHub username.
Now, we need to generate a DockerHub Personal Access Token, for that head over to your DockerHub Account, go to setting, then click on Personal Access Token and then click on the Generate new Token button. A new window will open (as shown in the image below. Give a token name and Access Permissions to Read & Write (It may vary according to your use case). Copy the generated token and create a GitHub Secret (like we did above) with the name DOCKERHUB_TOKEN
, and paste the copied token value in the secret value.
Once done we are all set to create a workflow. Make sure you are on the root of the project, create a dir name .github
inside that create a dir called workflows
and inside that create a YAML file with any name, we will name it dockerhub.yaml
. The complete path will look like this .github/workflows/dockerhub.yaml
. Now paste the below configuration. Don't commit it yet, first, we break down and understand the below configuration.
name: Build and Push Image to DockerHub
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: DockerHub Login
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/devops:latest
platforms: linux/amd64,linux/arm64,linux/arm/v7
In this section, we are triggering the workflow when a release is created. We can modify the on:
trigger according to our release flow, like triggering the workflow when a tag is pushed, etc. Then we are using Ubuntu as a runner and then checking out the repo code so that the workflow has access to our repo code.
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Now in this part, we are logging into the DockerHub with the credentials we provided via GitHub Secrets so that the workflow has the necessary permission to push the image to our DockerHub Account. Then we set up the Docker Buildx. Buildx is the real deal that will help us build the Multi-Arch images from the same Dockerfile.
- name: DockerHub Login
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
This is the final step of the workflow here we are building the image and pushing it to DockerHub. We can set a context
and file
, here the Dockerfile is in the root with the name Dockerfile. tags
helps in tagging the Docker image as we do locally. We can set multiple tags
apart from the latest
one, For example, we can automate unique image versioning by pulling the git tag pushed to trigger this workflow. So, if we push a Git tag with 1.2.3
, the image would be something like pradumnasaraf/devops:1.2.3
. Make sure you change the image name from devops
. Instead of hardcoding you can also set the repo name and get that value from the github
variable.
Lastly, we are we are providing for which platforms we need to build it for. we can give the values for platforms
by comma separation. Here we are building for linux/amd64
,linux/arm64
and linux/arm/v7
.
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/devops:latest
platforms: linux/amd64,linux/arm64,linux/arm/v7
That's it, that was all about the explanation workflow. Now commit the changes. Based on the type of trigger you set this workflow will run and push the images to DockerHub.
I have created a release on my DevOps repo with v2.3.3
. Now, It will push an image with the latest
as well as 2.3.3
. It is gets the version number from the package.json
using an action to extract it. You can do this kind of workaround to make it more seamless and powerful.
Now, let's head over to DockerHub to check the pushed image. We can see it we have our image published on DockerHub with the OS/ARCH
we have provided.
Now the great part is that if someone pulls an image, for eg docker pull pradumnasaraf/devops
docker will pull the image for that architecture only we don't need to explicitly mention that.
That's come to the end of this blog. As usual, glad you made it to the end—thank you so much for your support. I regularly share Docker tips on Twitter. You can connect with me there.