In Docker containers, images can be built for multiple architectures ranging from AMD64 to ARM. When building apps in different architectures, the software requirements will differ. If an app builds successfully on AMD64, it is not given that it will build successfully on ARM.
We will tackle an issue with a JS application's Docker container image built on ARM and see how we can leverage multi-stage builds to build multi-architecture apps in Docker for all architectures in this blog.
Minimal knowledge of multi-stage Docker builds is preferred.
As we previously discussed, applications exhibit certain kinds of behaviour when built on an architecture they may not natively support. In our case, it's not specifically the application itself.
Node JS works very well on both ARM and AMD64-based architecture. It's about dependencies and microcomponents of an application that may not be provided natively for architecture and may require building for that specific architecture on the user's side.
Here in our case, the
npm i command fails when built on ARM, but the same thing on AMD64 works perfectly. To be fair, the command would not have failed on ARM if missing dependency(s) were provided at build time. Using Docker, we expect the app to work with the commands provided in the
Dockerfile. We never think about providing extra dependencies.
The error log below is a perfect example of errors like these.
If you study the logs properly, it's searching for dependencies like
GCC, etc., which are not available in the Docker image and are required by
npm as it wants to compile some packages to work in an ARM-based architecture.
To avoid such problems, you could simply specify the build target architecture as ARM using the parameter
--platform linux/amd64 . It will take more time to build compared to host native architecture builds.
So, to solve this, we will use some Docker native environment variables to detect the host architecture. We will then run a specific build stage only if the host architecture matches the target architecture; otherwise, a different stage will be run.
The following is the Dockerfile that we will use:
How it works?
In this Docker build, we will build our Docker image according to the target architecture and make some modifications along the way for an uninterrupted build process.
- The initial stage will copy the required files, which are architecture agnostic. This means they can be used in either architecture.
- The second and third stages are the most interesting ones. Each of these stages copies the
package.jsonfile and run the
npm icommand. The
stage-arm64stage differs because it also consists of extra dependencies which will be required by
npmduring package installation.
- Finally, the target architecture is evaluated in the last stage, and the build is performed. Note that Docker will not run both the ARM and AMD architectures at the same time. Either of these stages will be run according to the architecture of the Docker build at that time.
In this same way, you can write Dockerfiles that are architecture aware and will work irrespective of the architecture.
But you need to know the dependencies required for each architecture so the build does not error out due to missing dependencies.
Please comment below if you have any queries, I periodically try to update my articles to ensure legibility!