Docker Multi-Arch: Resolving the ‘exec format error’ on M1/M2 and Cloud ARM

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

The ‘exec format error’ Nightmare

If you are using a Macbook with M1/M2 chips or renting an ARM VPS (such as AWS Graviton or Oracle Cloud) to save costs, you have likely encountered this frustrating situation. You build a Docker image with great effort on your personal machine, push it to Docker Hub, but when you pull it to the server, you immediately receive the message: standard_init_linux.go:211: exec user process caused "exec format error".

This error occurs due to CPU architecture inconsistency. Your personal machine might be running on an ARM chip, while the server uses an Intel/AMD (x86_64) chip. Previously, I had to manually build on two different machines and tag them like my-app:v1-arm64 and my-app:v1-amd64. This method is manual, time-consuming, and extremely prone to errors when deploying large systems.

After many “painful” experiences, I switched completely to using Docker Buildx. This is a powerful plugin that helps create a single image that runs smoothly on all types of chips. Here is my practical experience on how to set it up and use it most effectively.

Why is Buildx Superior to Standard Docker Build?

The default docker build command only creates images for the current machine’s architecture. If you use an Intel machine, it builds for x86. If you use an M1, it builds for ARM.

In contrast, Buildx leverages Moby BuildKit and QEMU to emulate different hardware environments. My favorite feature is the ability to create a Manifest List. When you perform a docker pull, Docker automatically detects your hardware to choose the most suitable version. You no longer have to struggle with specifying specific tags for each CPU type.

Installation and Environment Setup

Most Docker Desktop versions on Windows and Mac now come with Buildx integrated. To be sure, check with the command: docker buildx version.

If the terminal returns a version, you’re ready. The next step is to install QEMU. This tool helps the computer “pretend” to be a different chip to execute instructions during the build process.

docker run --privileged --rm tonistiigi/binfmt --install all

I usually run this command once when setting up a new machine. It registers binary interpreters for different architectures into the Linux kernel.

Detailed Builder Instance Configuration

Docker’s default driver often doesn’t support building multiple architectures simultaneously. Therefore, we need to create a new “virtual build machine” (builder instance).

Step 1: Create a new builder

Name it mybuilder for easy management:

docker buildx create --name mybuilder --use

Step 2: Start the builder

Activate it with the following command:

docker buildx inspect --bootstrap

A list of supported platforms will appear, for example: linux/amd64, linux/arm64, linux/riscv64.... If you see both amd64 and arm64, you’re ready for action.

Practicing Multi-Arch Image Building

Suppose I have a Node.js project with a basic Dockerfile. To build for both Intel and ARM, I use the command:

docker buildx build --platform linux/amd64,linux/arm64 -t your-username/my-app:latest --push .

Practical Note: You should use the --push flag to push directly to the Registry. Currently, --load does not well support storing multi-arch images directly in Docker’s local storage. Buildx will automatically combine the versions into a single Manifest before pushing to the Hub.

During execution, you’ll see Buildx processing two threads in parallel. If the code has libraries that need compilation (like node-gyp), building for ARM on an Intel machine can be 5-10 times slower due to the QEMU emulation layer. Please be patient!

Checking and Verifying Results

How can you be sure the image supports multiple platforms? I usually use these two methods:

  • Method 1: Check the UI: Go to Docker Hub, Tags section. You will see the latest tag displaying both linux/amd64 and linux/arm64 icons.
  • Method 2: Use the Terminal: Run the command docker buildx imagetools inspect your-username/my-app:latest. It will return detailed checksums for each CPU type.

Tips to Avoid Common Issues

  • Base Image: Always check if the image in the FROM line supports multi-arch. Official images like node, python, and alpine have excellent support.
  • Build Speed: For large projects, emulation will be very slow. In an enterprise environment, I often connect a real Intel machine and a real ARM machine to the same builder instance for native building (which is significantly faster).
  • Binary Download Commands: Avoid using curl to directly download a specific file like tool-linux-x64. Instead, use the ${TARGETARCH} environment variable so Docker can automatically choose the correct download version.

Mastering Docker Buildx makes your CI/CD process much more professional. You can flexibly switch between cloud providers to optimize costs without worrying about compatibility issues. Good luck with your implementation!

Share: