How to build Docker Images for Apple Silicon (aka M1 Chip)

Sergey Buryking

Software Engineer
April 20th, 2021

Background

Apple released a new MacBooks based on a M1 chip a while ago. Unlike all previous Apple laptops (which were Intel based), M1 has a different architecture — arm64 instead Intel's amd64.

New architecture means that the most software products should be repackaged: at least ones written in compiled-languages. It applies to Jitsu which is written mostly in Go. However, it's not 100% true: Apple went to great lengths to make old compiled code compatible with new architecture. The piece of software that makes is possible is called Roseta 2.

Docker tried to catch up as well: the M1 support came out a few weeks ago. However, things do not work quite as seamless as:

Docker images built for amd64 (Intel) will run on M1 Macs. But the stability isn't guaranteed. You should ship a second version of your image for arm64 (M1 chip).

Docker relies on qemu to emulate Intel's architecture on M1 chips. Unfortunately, QEMU doesn't work quite well: However, attempts to run Intel-based containers on Apple Silicon machines can crash as QEMU sometimes fails to run the container. Filesystem change notification APIs (e.g. inotify) do not work under QEMU emulation [...]. Therefore, we recommend that you run ARM64 containers on Apple Silicon machines. These containers are also faster and use less memory than Intel-based containers. (See docker documentation for more details)

Why care about M1 macs at all? Aren't all servers still run on Intel anyway? That's a great question! It's true, most of the servers runs on Intel platform. However, at Jitsu we committed to provide an excellent developer's experience. We're sure that stable work at a developer's machine is as much important as server's stability. Besides, 1/2 of our dev team already got a new M1 Macs. So starting from 1.29.0 we decided to ship all releases as dual-architecture images.

We're convinced that all other open-source project should do the same. Therefore, we decided to share our experience with multi-architecture docker builds.

How to build an image for arm64

Luckily, Docker has announced the support of cross CPU architecture builds a few weeks ago. buildx - an experimental feature that allows building images for a certain architecture - arm64, amd64, etc.

Step 1. Enable experimental features

If you’re using Docker Desktop for Mac you should just enable experimental features on the Docker CLI if you haven’t already – just edit ~/.docker/config.json to include the following:

{
  ...
  "experimental": "enabled"
}

Restart your Docker Desktop after that

If you’re using Linux, just run the following command:

docker run --privileged --rm docker/binfmt:a7996909642ee92942dcd6cff44b9b95f08dad64

And restart Docker with service docker restart

Step 2. Create a new builder instance

Create a new builder instance:

docker buildx create --use

It enables multiple builds in parallel. Make sure that new builder instance is created:

docker buildx ls

Output:

$ docker buildx ls
NAME/NODE     DRIVER/ENDPOINT       STATUS  PLATFORMS
mystifying_bell * docker-container
  mystifying_bell0 unix:///var/run/docker.sock inactive
default      docker
  default     default           running linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6

Step 3. Build multi-architecture image

Build your multi-architecture image:

docker buildx build --platform linux/amd64,linux/arm64 -t company/image_name .

platform flag specifies for which platforms Docker image will be built. Docker support 10 platforms, but probably you shouldn use only linux/amd64 (Intel) and linux/arm64 (M1):

Docker Hub result
Docker Hub result

Build multi-architecture images on CircleCI

At Jitsu we use CircleCI for building and pushing our Docker images to Docker Hub. There is an example of .circleci/config.yaml with building multi-architecture Docker images on every git push to master branch:

install_buildx: &install_buildx
  run:
    name: install docker buildx
    command: |
      mkdir -vp ~/.docker/cli-plugins/
      curl --silent -L --output ~/.docker/cli-plugins/docker-buildx https://github.com/docker/buildx/releases/download/v0.3.1/buildx-v0.3.1.linux-amd64
      chmod a+x ~/.docker/cli-plugins/docker-buildx
      docker buildx version
      docker context create jitsu
      docker buildx create jitsu --use
      docker run --privileged --rm tonistiigi/binfmt --install all

build_jitsu: &build_jitsu
  run:
    name: Build and Push Jitsu Docker image
    command: |
      docker login -u $DOCKER_LOGIN -p $DOCKER_PWD
      DOCKER_BUILDKIT=1 docker buildx build --platform linux/amd64,linux/arm64 --push -t $IMAGE_NAME:$IMAGE_VERSION -f $DOCKER_FILE .

version: 2.1

jobs:
  build-latest-jitsu-docker:
    environment:
      IMAGE_NAME: jitsucom/server
      IMAGE_VERSION: latest
      DOCKER_FILE: server.Dockerfile
    docker:
      - image: cimg/go:1.14.15
    steps:
      - checkout
      - setup_remote_docker:
          version: 19.03.13
      - <<: *install_buildx
      - <<: *build_jitsu

workflows:
  version: 2.1
  build-docker:
    jobs:
      - build-latest-jitsu-docker:
          context: jitsu
          filters:
            branches:
              only: master

About Jitsu

Jitsu is an open-source data integration platform offering features like pulling data from APIs, streaming event-base data to DBs, multiplexing and many others.
© Jitsu Labs, Inc

2261 Market Street #4109
San Francisco, CA 94114