After using a M1 Mac Mini for a while, I upgraded my personal laptop to a M1 Max 14” MacBook Pro, and I’ve started using 64-bit ARM containers on AWS, Oracle Cloud, and Hetzner’s cloud service.
Previously, I used Docker Buildx and QEMU to build arm64 containers, but this is really slow - so I went looking at options to build natively.
GitLab CI provide an arm64 runner build, and Oracle Cloud have an “Always Free” Ampere option - with up to 24GB of RAM and up to 4 VCPUs.
This is how I set it up.
Preparing the x86_64 runner
- Install an OS on your runner. This is a VM on my own hardware, so I went with Debian.
- Install up to date Docker. Follow the official instructions.
- Install the Gitlab CI runner. Follow the official instructions.
- Register your runner. Follow the official instructions. Give it a unique tag - I used
amd64
. - Add some default environment variables to
/etc/gitlab-runner/config.toml
:concurrent = 1 check_interval = 0 shutdown_timeout = 0 [session_server] session_timeout = 1800 [[runners]] name = "x86_64 runner" url = *snip* id = *snip* token = *snip* token_obtained_at = *snip* token_expires_at = *snip* executor = "docker" [runners.cache] MaxUploadedArchiveSize = 0 [runners.docker] tls_verify = false image = "docker:stable" privileged = true disable_entrypoint_overwrite = false oom_kill_disable = false disable_cache = false volumes = ["/certs/client", "/cache"] shm_size = 0
- Restart the gitlab runner:
systemctl restart gitlab-runner
Preparing the arm64 runner
- Install an OS on your runner. Oracle limit their free service to Oracle Linux or Ubuntu, so I went with Ubuntu.
- Install up to date Docker. Follow the official instructions.
- Install the Gitlab CI runner. Follow the official instructions.
- Register your runner. Follow the official instructions. Give it a unique tag - I used
arm64
. - Add some default environment variables to
/etc/gitlab-runner/config.toml
:concurrent = 1 check_interval = 0 shutdown_timeout = 0 [session_server] session_timeout = 1800 [[runners]] name = "ARM64 OCI runner" url = *snip* id = *snip* token = *snip* token_obtained_at = *snip* token_expires_at = *snip* executor = "docker" [runners.cache] MaxUploadedArchiveSize = 0 [runners.docker] tls_verify = false image = "docker:stable" privileged = true disable_entrypoint_overwrite = false oom_kill_disable = false disable_cache = false volumes = ["/certs/client", "/cache"] shm_size = 0
- Restart the gitlab runner:
systemctl restart gitlab-runner
Making a multi-platform build
Use a template a bit like:
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
amd64-docker-build-latest:
stage: build
tags:
- amd64
script:
- docker build -t "$CI_REGISTRY_IMAGE:latest-amd64" .
- docker push "$CI_REGISTRY_IMAGE:latest-amd64"
only:
- master
- main
arm64-docker-build-latest:
stage: build
tags:
- arm64
script:
- docker build -t "$CI_REGISTRY_IMAGE:latest-arm64" .
- docker push "$CI_REGISTRY_IMAGE:latest-arm64"
only:
- master
- main
manifest-docker-build-latest:
stage: deploy
script:
- docker manifest create "$CI_REGISTRY_IMAGE:latest" "$CI_REGISTRY_IMAGE:latest-amd64" "$CI_REGISTRY_IMAGE:latest-arm64"
- docker manifest push "$CI_REGISTRY_IMAGE:latest"
only:
- master
- main
amd64-docker-build:
stage: build
tags:
- amd64
script:
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-amd64" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-amd64"
except:
- master
- main
arm64-docker-build:
stage: build
tags:
- arm64
script:
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-arm64" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-arm64"
except:
- master
- main
manifest-docker-build:
stage: deploy
script:
- docker manifest create "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-amd64" "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-arm64"
- docker manifest push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG"
except:
- master
- main
At this point, you should have a working multi-platform image!