Build Multi Arch Docker Images On Gitlab

How to build docker images on Gitlab so that they run on both Intel and ARM architectures?

With the rise of ARM devices in both the server and desktop markets, it has become important to build ARM versions of your software along with the x86 versions. Similarly, if you are distributing your applications with docker, you need to buid ARM compatible docker.

Here’s the .gitlab-ci for gitlab CI/CD:

stages:
  - create-image

variables:
  DOCKER_BUILDKIT: 1

create-latest-image:
  stage: create-image
  image: docker:latest
  services:
   - name: docker:dind
     command: ["--experimental"]
  script:
    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
    - apk add curl
    - mkdir -vp ~/.docker/cli-plugins/
    - curl --silent -L "https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.linux-amd64" > ~/.docker/cli-plugins/docker-buildx
    - chmod a+x ~/.docker/cli-plugins/docker-buildx
    - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
    - docker buildx create --use
    - docker buildx inspect --bootstrap
    - docker buildx build --push --platform linux/amd64,linux/arm64 -t $CI_REGISTRY_IMAGE:latest .
    - docker manifest inspect $CI_REGISTRY_IMAGE:latest
  only:
    - master

Slighty refactored version, if there are multiple jobs to be run, also uses build args

stages:
  - create-image
  - production-release

variables:
  DOCKER_BUILDKIT: 1

.create_multi_arch_image:
  image: docker:latest
  services:
   - name: docker:dind
     command: ["--experimental"]
  before_script:
    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
    - apk add curl
    - mkdir -vp ~/.docker/cli-plugins/
    - curl --silent -L "https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.linux-amd64" > ~/.docker/cli-plugins/docker-buildx
    - chmod a+x ~/.docker/cli-plugins/docker-buildx
    - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
    - docker buildx create --use
    - docker buildx inspect --bootstrap


create-latest-image:
  extends: .create_multi_arch_image
  stage: create-image
  script:
    - docker buildx build --build-arg CI_JOB_TOKEN=$CI_JOB_TOKEN --push --platform linux/amd64,linux/arm64 -t $CI_REGISTRY_IMAGE:latest .
    - docker manifest inspect $CI_REGISTRY_IMAGE:latest
  only:
    - master

create-versioned-image:
  extends: .create_multi_arch_image
  stage: production-release
  script:
    - docker buildx build --build-arg CI_JOB_TOKEN=$CI_JOB_TOKEN --push --platform linux/amd64,linux/arm64 -t $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG .
    - docker manifest inspect $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
  only:
    - tags

Here are the interesting pieces:

This environment variable is required for buildx

variables:
  DOCKER_BUILDKIT: 1

The job is run in a docker container using the docker in docker image

image: docker:latest

The docker daemon needs to be run in the experimental mode

services:
  - name: docker:dind
    command: ["--experimental"]

As of the time of writing this, the buildx plugin is not installed by default and needs to be installed manually

- apk add curl
- mkdir -vp ~/.docker/cli-plugins/
- curl --silent -L "https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.linux-amd64" > ~/.docker/cli-plugins/docker-buildx
- chmod a+x ~/.docker/cli-plugins/docker-buildx

The multiarch/qemu-user-static docker image is used to emulate ARM64 environemnt

- docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

Since multiple platforms feature is currently not supported for docker driver, create a new builder instance and set it as the current instance

- docker buildx create --use

Confirm that the required platforms are available (optional)

- docker buildx inspect --bootstrap

Build and push the latest image

docker buildx build --push --platform linux/amd64,linux/arm64 -t $CI_REGISTRY_IMAGE:latest .

Check the manifest for the image that was pushed

docker manifest inspect $CI_REGISTRY_IMAGE:latest

References