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.