23. May 2021 6 min read

Build docker container for multiple architectures on Gitlab CI

Gitlab CI is a great platform to build your docker images right from your application. But as an owner of mixed Kubernetes cluster with RaspberryPis and x86_64 CPU I need to make each image for multiple architectures. This is easy with the docker buildx command, but problem is that it is still considered experimental. So first thing we need to do is use a correct image with correct service in Gitlab CI. I use templates, simply because it is easier when you need to build nginx image, python image, database image, etc. So here is the template to enable experimental services and two variables for the image name and version:

variables:
  CONTAINER_TEST_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
  CONTAINER_RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest

.template: &docker_template
  image: docker:19.03.13-dind
  services:
    - name: docker:19.03.13-dind
      command: ["--experimental"]

Now we need to adjust some variables according to Gitlab Runner specification to enable building docker inside docker. I also add curl (docker-dind is based on alpine so you should use apk). As you will notice this is using the previous template to define the services.

.template: &docker_buildx
  <<: *docker_template
  stage: build
  variables:
    # Use TLS https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#tls-enabled
    DOCKER_HOST: tcp://docker:2375
    DOCKER_DRIVER: overlay2
    DOCKER_TLS_CERTDIR: ""
    DOCKER_CLI_EXPERIMENTAL: enabled
    BUILDX_VERSION: "0.4.2"
  before_script:
    - apk --no-cache add curl

I sneaked in BUILDX_VERSION variable in there for someone reading in details, because I wanted a bit more obvious position of the version, but that is not essential. Now lets focus on the script part of the .gitlab-ci.yml. First we need to extract architecture of the current runner, then we will use curl to download the correct docker-buildx binary, make it executable and then issue series of buildx commands to build for amd64 and arm/v7 images. In this series of commands you also find a docker login command which enables that we login to Gitlab-CI Container Registry because buildx build command has --push flag inside as it directly pushes to the Container registry.

  script:
    - export ARCH=$(uname -m)
    - case $ARCH in
        armv6*) ARCH="arm-v6";;
        armv7*) ARCH="arm-v7";;
        aarch64) ARCH="arm64";;
        x86_64) ARCH="amd64";;
      esac
    - curl -L --fail --output /docker-buildx "https://github.com/docker/buildx/releases/download/v${BUILDX_VERSION}/buildx-v${BUILDX_VERSION}.linux-${ARCH}"
    - chmod a+x /docker-buildx
    - mkdir -p /usr/lib/docker/cli-plugins
    - cp /docker-buildx /usr/lib/docker/cli-plugins/docker-buildx
    - docker context create something
    - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
    - DOCKER_CLI_EXPERIMENTAL=enabled docker buildx create --use --name buildx-builder something
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - DOCKER_CLI_EXPERIMENTAL=enabled docker buildx inspect --bootstrap
    - echo $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
    - DOCKER_CLI_EXPERIMENTAL=enabled docker buildx build --platform linux/amd64,linux/arm/v7 --push -f Dockerfile -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .

So now these are just templates, they are not doing anything until this last template is called. Here you have the whole extract which will build your multiple architecture container on any runner (Raspberry PI runner, or normal amd64 runner).

stages:
  - build

variables:
  CONTAINER_TEST_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
  CONTAINER_RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest

.template: &docker_template
  image: docker:19.03.13-dind
  services:
    - name: docker:19.03.13-dind
      command: ["--experimental"]

.template: &docker_buildx
  <<: *docker_template
  stage: build
  variables:
    # Use TLS https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#tls-enabled
    DOCKER_HOST: tcp://docker:2375
    DOCKER_DRIVER: overlay2
    DOCKER_TLS_CERTDIR: ""
    DOCKER_CLI_EXPERIMENTAL: enabled
    BUILDX_VERSION: "0.4.2"
  before_script:
    - apk --no-cache add curl
  script:
    - export ARCH=$(uname -m)
    - case $ARCH in
        armv6*) ARCH="arm-v6";;
        armv7*) ARCH="arm-v7";;
        aarch64) ARCH="arm64";;
        x86_64) ARCH="amd64";;
      esac
    - curl -L --fail --output /docker-buildx "https://github.com/docker/buildx/releases/download/v${BUILDX_VERSION}/buildx-v${BUILDX_VERSION}.linux-${ARCH}"
    - chmod a+x /docker-buildx
    - mkdir -p /usr/lib/docker/cli-plugins
    - cp /docker-buildx /usr/lib/docker/cli-plugins/docker-buildx
    - docker context create something
    - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
    - DOCKER_CLI_EXPERIMENTAL=enabled docker buildx create --use --name buildx-builder something
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - DOCKER_CLI_EXPERIMENTAL=enabled docker buildx inspect --bootstrap
    - echo $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
    - DOCKER_CLI_EXPERIMENTAL=enabled docker buildx build --platform linux/amd64,linux/arm/v7 --push -f Dockerfile -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .

# End of templates, the real jobs start here
# ---
build:
  <<: *docker_buildx

I hope this helped, so leave a comment if I have missed something useful in the example.

Newest from this category: