Building Multi-Arch Images for Arm and x86 with Docker
In this blog post, I show how we can build Spring Boot Multi-Arch Images for Arm and x86 using Docker
Processor Architectures
There are different architectures on which processor are made.
Processors | Supporting Architecture |
Intel and AMD processors | x86/x86-64/AMD64 |
Apple M1 , Ampere A1, Graviton, Broadcom ( Used in Raspberry Pi) | ARM/ARM64/ARMHF |
While most of compute instances we use in the the cloud are based on x86 ( or x64/amd64) architecture. Cloud provides like Amazon ( Graviton processor ) and Oracle ( Ampere processor) are providing ARM based architecture compute instances.
Best part is Amazon and Oracle is charging less $$ per hour for ARM based compute instances compared to x86 based instances.
If software required by your application supports ARM architecture , then you can deploy application in ARM based compute instances and save some money.
By default when we build images with docker, it creates image based on the architecture of system.
For building images for each architecture, need to run the build command for each architecture.
Docker also provides buildx command which can be used to build multi-arch image in one go.
Multi-architecture images are beneficial in following scenarios.
- When you want to run your container locally on your x86-64 based machine and deploy on ARM based compute in the cloud
- When you want to run your container locally on your Apple M1/M2 based machine and deploy on x86-64 based compute in the cloud
Installing Buildx
If you have installed Docker Desktop, then buildx command should be available on your system.
If you installed docker in your Linux machine through command line, you need to separately install the buildx command.
- Run docker –help command check whether buildx command is available or not.
- If not, run following commands to install buildx command on Linux
RUN curl --silent -L https://github.com/docker/buildx/releases/download/v0.9.1/buildx-v0.9.1.linux-amd64 -o buildx-v0.9.1.linux-amd64
RUN chmod a+x buildx-v0.9.1.linux-amd64
RUN mkdir -p ~/.docker/cli-plugins
RUN mv buildx-v0.9.1.linux-amd64 ~/.docker/cli-plugins/docker-buildx
Code language: Java (java)
Note
While installing buildx separately, please download the appropriate file for your system architecture here.
Checking Supported Architectures
Once Buildx command is available , next you need to check the supported architecture by running following command.
<strong>$</strong>docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
default * docker
default default running 20.10.12 linux/arm64, linux/arm/v7, linux/arm/v6, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386
Code language: Java (java)
If you have installed Docker Desktop then all well known architectures should be listed.
In Linux OS, if you have installed Docker through cli commands, then you may not see either X86 or ARM related architectures depending on your system CPU architecture.
Installing Emulator
for building other architecture images that does not have native support from buildx tool, you need to install the emulators.
binfmt image can be used to install emulators for architectures your node does not have native support so that you can run and build containers for any architecture.
$ docker run --privileged --rm tonistiigi/binfmt --install all
installing: 386 OK
installing: s390x OK
installing: ppc64le OK
installing: mips64 OK
installing: amd64 OK
installing: mips64le OK
{
"supported": [
"linux/arm64",
"linux/amd64",
"linux/riscv64",
"linux/ppc64le",
"linux/s390x",
"linux/386",
"linux/mips64le",
"linux/mips64",
"linux/arm/v7",
"linux/arm/v6"
],
"emulators": [
"qemu-arm",
"qemu-i386",
"qemu-mips64",
"qemu-mips64el",
"qemu-ppc64le",
"qemu-riscv64",
"qemu-s390x",
"qemu-x86_64"
]
}
Code language: Java (java)
Now run the docker buildx ls command again to check the supported architectures.
Now it should list all the well known architectures.
docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
default * docker
default default running 20.10.12 linux/arm64, linux/arm/v7, linux/arm/v6, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386
Code language: Java (java)
Building Images
Let’s try to build the multi arch images for a spring boot application which I developed previously.
You can download the sources from GitHub
You can find below Docker file in the sources.
FROM adoptopenjdk/openjdk11:x86_64-alpine-jdk-11.0.14.1_1-slim
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
RUN addgroup -S springboot && adduser -S sbuser -G springboot
USER sbuser
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]
Code language: Java (java)
First let’s check the supported architectures by JVM being used in docker hub.
From the above, you can see that current JVM being used supports only AMD64( i.e. X86_64) architecture.
We need to replace the JVM which supports multiple architectures.
Let’ search for JVM images which supports both AMD64 and ARM architectures.
I have selected a JVM which supports both AMD64 and ARM64 architectures otherwise you need to maintain 2 different docker files for each architecture.
Let’s change the JVM in the docker file.
Note : I had to change the script to add user as my previous commands were giving error.
FROM adoptopenjdk/openjdk11:jdk-11.0.16.1_1-slim
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
RUN addgroup --system springboot && adduser --system sbuser && adduser sbuser springboot
USER sbuser
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]
Code language: Java (java)
Let’s build the image of spring boot application.
docker build . -t springboot-crud-app
Code language: Java (java)
Once image is built , we can check the architecture supported by image using inspect command.
docker inspect springboot-crud-app
Code language: Java (java)
[
{
...
"Architecture": "amd64",
"Os": "linux",
"Size": 409931847,
"VirtualSize": 409931847,
...
}
]
Code language: Java (java)
The output shows that image supports the AMD64 architecture.
I have built above image on intel based system so by default image was built to support the AMD64 architecture. If you build the same image on ARM based system, the default image built will support the ARM architecture.
If you want to build the ARM supported image on intel based system, you need to specify the platform while building image.
docker build --platform=linux/arm64 . -t springboot-crud-app
Code language: Java (java)
Now if you inspect the image, you should see supported architecture as arm.
[
{
"Architecture": "arm64",
"Os": "linux",
"Size": 400209740,
"VirtualSize": 400209740,
}
]
Code language: Java (java)
Builing Multi Arch images with Single Command
If you try to build the both ARM and Ax86 supported images with single command
docker build --platform=linux/amd64,linux/arm64 . -t springboot-crud-app
Code language: Java (java)
You will see below error message
[+] Building 0.0s (0/0)
ERROR: multiple platforms feature is currently not supported for docker driver. Please switch to a different driver (eg. “docker buildx create –use”)
Default docker builder can not build multi arch images with single command.
But running one commands for each supported architecture become laborius.
Docker supports building multi arch images with single command but we need to create custom builder for that.
Create custom docker driver with following command.
docker buildx create --name multiarch
Code language: Java (java)
We confirm creation of driver creation with following command
docker buildx ls
Code language: Java (java)
You should see output like below. At the moment created driver is inactive state.
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
multiarch docker-container
multiarch0 npipe:////./pipe/docker_engine inactive
default * docker
default default running 20.10.17 linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
desktop-linux docker
desktop-linux desktop-linux running 20.10.17 linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
Code language: Java (java)
We can switch to custom builder with following commnad.
docker buildx use multiarch
Code language: Java (java)
You can verify the current builder
docker buildx inspect
Name: multiarch
Driver: docker-container
Nodes:
Name: multiarch0
Endpoint: npipe:////./pipe/docker_engine
Status: inactive
Platforms:
Code language: Java (java)
You need to make the builder active before using it.
docker buildx inspect --bootstrap
Code language: Java (java)
[+] Building 44.4s (1/1) FINISHED
=> [internal] booting buildkit 44.4s
=> => pulling image moby/buildkit:buildx-stable-1 36.7s
=> => creating container buildx_buildkit_multiarch0 7.6s
Name: multiarch
Driver: docker-container
Nodes:
Name: multiarch0
Endpoint: npipe:////./pipe/docker_engine
Status: running
Buildkit: v0.10.5
Platforms: linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
Code language: Java (java)
Next we can build multi arch image with below single command and push them to docker registry.
docker buildx build --platform linux/amd64,linux/arm64 --push -t "docker.io/sureshgkhyd/sb-crud-multiarch" .
Code language: Java (java)
[+] Building 349.2s (14/14) FINISHED
=> [internal] load build definition from Dockerfile 0.3s
=> => transferring dockerfile: 295B 0.0s
=> [internal] load .dockerignore 0.3s
=> => transferring context: 2B 0.0s
=> [linux/arm64 internal] load metadata for docker.io/adoptopenjdk/openjdk11:jdk-11.0.16.1_1-slim 32.8s
=> [linux/amd64 internal] load metadata for docker.io/adoptopenjdk/openjdk11:jdk-11.0.16.1_1-slim 32.7s
=> [auth] adoptopenjdk/openjdk11:pull token for registry-1.docker.io 0.0s
=> [internal] load build context 32.0s
=> => transferring context: 38.38MB 30.8s
=> [linux/amd64 1/3] FROM docker.io/adoptopenjdk/openjdk11:jdk-11.0.16.1_1-slim@sha256:2bae39942b27c6b1a33ea95d09f6c5b1aee743ab72639cfbd1706dd1bd0ac451 130.4s
=> => resolve docker.io/adoptopenjdk/openjdk11:jdk-11.0.16.1_1-slim@sha256:2bae39942b27c6b1a33ea95d09f6c5b1aee743ab72639cfbd1706dd1bd0ac451 0.1s
=> => sha256:cc3add041a51a4805ca754cae7bf40c33fd2f942f294dcf330126683ab268b61 127.19MB / 127.19MB 69.7s
=> => sha256:4d271d68e46ef2be5d1565785ed38c54ad188f538564de53126eda7574c1c2ea 125.00MB / 125.00MB 69.0s
=> => sha256:43cc645298984b573b21d1f701c215dba734944018236192c1583a2d20753fd5 5.09kB / 5.09kB 2.7s
=> => sha256:7506f0056329a0e2b4995a9e7f75939b283585ab0dbb0223b8101a77add16263 15.87MB / 15.87MB 13.7s
=> => sha256:4e7e0215f4adc2c48ad9cb3b3781e21d474b477587f85682c2e2975ae91dce9d 27.20MB / 27.20MB 21.7s
=> => extracting sha256:4e7e0215f4adc2c48ad9cb3b3781e21d474b477587f85682c2e2975ae91dce9d 35.5s
=> => extracting sha256:7506f0056329a0e2b4995a9e7f75939b283585ab0dbb0223b8101a77add16263 10.1s
=> => extracting sha256:43cc645298984b573b21d1f701c215dba734944018236192c1583a2d20753fd5 0.2s
=> => extracting sha256:4d271d68e46ef2be5d1565785ed38c54ad188f538564de53126eda7574c1c2ea 36.7s
=> [linux/amd64 2/3] COPY target/*.jar app.jar 0.9s
=> [linux/arm64 2/3] COPY target/*.jar app.jar 1.0s
=> [linux/amd64 3/3] RUN addgroup --system springboot && adduser --system sbuser && adduser sbuser springboot 4.2s
=> [linux/arm64 3/3] RUN addgroup --system springboot && adduser --system sbuser && adduser sbuser springboot 16.9s
=> exporting to image 167.5s
=> => exporting layers 17.1s
=> => exporting manifest sha256:80ca0495c49d4a532455209bf3fa17f76e8d0e3053f38fb354d4d6e04d57fbcf 0.1s
=> => exporting config sha256:21e9d03a6b5a4ea50cceee78b2677748080cd011c908a3546409cc5531a9cbc0 0.0s
=> => exporting manifest sha256:5d0c4747b331bbd6e898621766d259c35f4395026154001707a74576e60efd3a 0.1s
=> => exporting config sha256:e5626aa73c5aba8d4f9d350fc298985715e3739225a585935f1072f49d0f8c1d 0.0s
=> => exporting manifest list sha256:9e1967fd85c2383dd66cbbe71dbb8f165f9b1c8bdee72108745cf7ab01770f6e 0.0s
=> => pushing layers 148.2s
=> => pushing manifest for docker.io/sureshgkhyd/sb-crud-multiarch:latest@sha256:9e1967fd85c2383dd66cbbe71dbb8f165f9b1c8bdee72108745cf7ab01770f6e 1.7s
=> [auth] sureshgkhyd/sb-crud-multiarch:pull,push token for registry-1.docker.io
Code language: Java (java)
One disadvantage of using single command to build multiple arch images, we can not view them in our local system , we can only push them to registry directly.
If we look at the docker registry, we can see that the pushed image supports AMD64 and ARM64 architectures.
If you want to run the platform specific docker image, we can use platform option with run command.
docker run --platform=linux/amd64 sureshgkhyd/sb-crud-multiarch
Code language: Java (java)
Running other architecture image via emulation
Since we have installed emulator, we might be able to run images of other architecture not supported by current system architecture.
I was able to run the above built arm based sb-crud-multiarch image on my intel based system with following command.
docker run --platform=linux/arm64 sureshgkhyd/sb-crud-multiarch:latest
Code language: Java (java)
Docker desktop was showing that image was running via emulation so it may fail or run slowly.
Note
When try to run the images related to unsupported architectures via emulation it is not guaranteed to run always.