Docker Multi-Platform Build
Lately, I attempted to execute a tailor-made image on my Raspberry Pi but encountered the error message
exec /usr/bin/sh: exec format error. After searching online, I discovered that this issue arose from an architectural incompatibility. The initial image was constructed on an AMD64 system, whereas the Raspberry Pi operates on an ARM-based machine, causing the container to not start. In this article, we will explore the process of creating multi-platform Docker images utilizing Docker BuildKit.
What is a Docker buildkit?
Docker BuildKit is an advanced toolkit for building Docker images, introduced as an opt-in feature in Docker 18.09. It offers enhanced performance, improved cache management, and additional features in comparison to the traditional Docker image-building process.
Some notable features of Docker BuildKit include:
- Concurrent build stages: BuildKit allows multiple build stages to run simultaneously, which can significantly reduce build times.
Improved cache management: BuildKit intelligently manages and shares the cache between different builds, leading to better utilization of system resources and faster builds.
- Incremental context sending: It only sends the changes in the build context to the daemon, rather than sending the entire context each time, which can save bandwidth and accelerate build times.
- Extensible frontend: BuildKit supports customizable frontends, enabling developers to extend the build process using different languages and tools.
- Mount types: It introduces new mount types like cache, secret, and ssh, which provide better control over the build process and enhance security.
To enable BuildKit, set the environment variable DOCKER_BUILDKIT=1 before running your Docker build command or update the Docker configuration file to use BuildKit by default. This advanced toolkit has been designed to improve the overall experience of building Docker images and to address the limitations of the traditional build system.
As of Docker version 23, BuildKit is enabled by default.
To view existing BuildKit builder, we run the following command:
docker buildx ls
You will see an output similar to the one below:
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS default * docker default default running 23.0.2 linux/amd64, linux/386 rootless docker rootless rootless running 23.0.2 linux/amd64, linux/386
By default, docker engine uses the default builder which uses a
docker driver. In order to support multi-platform build, we will need a builder using the
First step, we will create a new context to be used by the new builder.
docker context create tls-environment
Next, we will create a new builder that will build image for both AMD64 and ARM64 architectures.
docker buildx create tls-environment \ --name multiarch --driver docker-container \ --platform linux/amd64,linux/arm64 \ --bootstrap --use
Let's break down the command and its options:
docker buildx create tls-environment: This command initializes a new builder instance using the specified Docker context "tls-environment." The context includes the necessary configuration for connecting to the Docker daemon, including TLS settings if required.
--name multiarch: The --name option assigns a custom name to the builder instance, in this case,
--driver docker-container: This option specifies the build driver to be used for the builder instance. The
docker-containerdriver allows you to build Docker images using a containerized environment.
--platform linux/amd64,linux/arm64: The --platform option defines the target platforms for the build. In this case, the builder instance will target both the Linux AMD64 and Linux ARM64 architectures.
--bootstrap: This flag bootstraps the builder instance, which means it sets up the necessary environment, including creating and starting a new build container.
--use: The --use flag instructs Docker to use the newly created builder instance as the default builder for subsequent docker buildx commands.
So, this command creates a new builder instance named
multiarch using the
tls-environment Docker context, with support for Linux AMD64 and Linux ARM64 platforms, and the
docker-container driver. It bootstraps the builder and sets it as the default builder for subsequent Docker Buildx commands.
After executing the above command, you will see the following output:
[+] Building 2.4s (1/1) FINISHED => [internal] booting buildkit 2.4s => => pulling image moby/buildkit:buildx-stable-1 1.9s => => creating container buildx_buildkit_multiarch0 0.6s multiarch
When using Docker Buildx to create a builder instance, it creates a container named buildx_buildkit_multiarch0 (or similar, depending on the instance name). If you need the builder to connect to sites with self-signed certificates, you must update the CA certificates inside the container. As the
moby/buildkit:buildx-stable-1 image is Debian-based, you can follow these steps to update the CA certificates:
- Copy the self-signed certificate file into the container. Replace
/path/to/your/certificate.crtwith the actual path to your certificate file:
docker cp /path/to/your/certificate.crt buildx_buildkit_multiarch0:/usr/local/share/ca-certificates/
- Run the
update-ca-certificatescommand in the container to update the trusted CA.
docker exec -t buildx_buildkit_multiarch0 update-ca-certificates
Building and pushing an image
Unfortunately, it is not possible to have build and push in two separate commands when using the
buildx command to build. Therefore, we will do this in one step using the command below:
docker buildx build --platform linux/amd64,linux/arm64 -t your-registry/your-image-name:your-tag --push .
For more information on using a private/mirror registry, you may wanna checkout the BuildKit TOML configuration file found in the reference section.