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.

ProcessorsSupporting 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.

  1. Run docker –help command check whether buildx command is available or not.
  2. 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/386Code 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/386Code 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-appCode language: Java (java)

Once image is built , we can check the architecture supported by image using inspect command.

docker inspect  springboot-crud-appCode 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-appCode 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-appCode 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 multiarchCode language: Java (java)

We confirm creation of driver creation with following command

 docker buildx lsCode 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/v6Code language: Java (java)

We can switch to custom builder with following commnad.

 docker buildx use multiarchCode 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/v6Code 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-multiarchCode 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:latestCode 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.

Similar Posts