As you know Apple has begun the transition from Intel x86_64 processors to ARM64-based Apple silicon chips in Mac computers. Since then, the company launched several M1 processors: apart from plain M1 chip, the product line includes M1 Pro, M1 Max, and M1 Ultra, the last one being the most powerful so far with 20 CPU cores and 64 GPU cores. You can read more about M1 features in perks in our previous article dedicated to the topic.
But there are still many jokes about some software that is lacking from the platform. The real challenge is to roll out programs that use low-level knowledge of the operating system and processor. Containerization and JVM are good examples of such technologies. They became friends not so long ago, and now we use the JDK in Docker as one of the main combinations of tools for everyday tasks. Many are worried whether or not they will be able to continue developing with the latest hardware. In my opinion, the answer is yes, but you need to check your toolbox. Let’s do this with a specific example. Moreover, we will not focus on the application code, but on what is needed for assembly and packaging.
Table of Contents
Installation
So how would we build, run, and package some typical applications? For instance, I took the Spring Petclinic sample project. As you clone the sources it is really easy to build and run a jar file. As usual, I start with the Liberica JDK. No need for Rosetta 2 at this step, just download native JDK 11 assembly for macOS ARM 64 bit.
I recommend installing .dmg, the binaries are notarized and the installer is simple and friendly.
Then, download Maven or use Maven Wrapper (mvnw) to build the project, and run it as usual to check the build result:
% java -jar target/spring-petclinic-2.4.2.jar
The application will respond on http://localhost:8080 Note again, it is the macos-aarch64 JDK.
Now, what about Docker? Docker is now compatible with Apple silicon, so there is no need for translator software apart from specific cases described on the developer’s website. What is important, it supports multi-platform images, so it is possible to build and run images for both x86 and ARM processors.
Install Docker:
Launch Docker Desktop, it will ask for privileges once to finish the setup.
Now it is time to use the Terminal.
Run application in a container
Let’s get the JDK image!
% docker pull bellsoft/liberica-openjdk-alpine-musl:11
It is Liberica JDK 11 for linux-aarch64. It downloads almost instantly, as the image is just 102 MB on disk (read how it was done in this article):
If we check how the hardware is represented in the container, we will see that just as documentation says by default four of eight CPUs are exposed, they are (effectively) virtualized. The system inside is Alpine Linux, so standard Linux tools will show that:
% docker run -it bellsoft/liberica-openjdk-alpine-musl:11 \
tail /proc/cpuinfo
processor : 3
BogoMIPS : 48.00
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 asimddp sha512 asimdfhm dit uscat ilrcpc flagm ssbs sb paca pacg dcpodp flagm2 frint
CPU implementer : 0x00
CPU architecture: 8
CPU variant : 0x0
CPU part : 0x000
CPU revision : 0
The JDK inside is built for Linux with musl C library and aarch64 CPU, so it is easy to run our example in the container:
% docker run -it -v $(pwd)/target:/target -p 8080:8080 \
bellsoft/liberica-openjdk-alpine-musl:11 java -jar \
/target/spring-petclinic-2.4.2.jar
No new image is created here; the jar file just picked from the volume. And the app is again available at http://localhost:8080:
Building a container image
Of course it is possible to create a Docker image with the application. A simple Dockerfile might look like this:
FROM bellsoft/liberica-openjdk-alpine-musl:11
WORKDIR /
ARG JAR=spring-petclinic-2.4.2.jar
ADD /target/$JAR app.jar
EXPOSE 8080
CMD java -jar app.jar
And the image is built as usual like
% docker build -f ./Dockerfile -t my/petclinic-m1 .
And run like
% docker run -it my/petclinic-m1
Cross-build for x86
What’s next? For example, you can push this image and deploy it out of the box on AWS EC2 M6g instances in your fleet. They are also powered by Graviton 2 ARM64 CPUs, and the image is created on ARM64 for the same platform. But this image won’t run on x86_64 (:amd64) machines (“exec format error”)! Here we should just use a cross-build with the Buildx plugin:
% docker buildx build --platform linux/amd64 \
-f ./Dockerfile -t my/petclinic-x86 .
This new image is based on Alpine Linux and the Liberica JDK for x86, and can finally be deployed on x86_64 instances for testing or production.
Conclusion
A few more topics worth touching on are development, stability, and speed. IntelliJ IDEA with native support for M1 has been available since the end of 2020. It means that you can not only clone and build your application, but develop it as usual. The performance of native JVM is better than x86 emulation; you can read about this in a separate blogpost.
Petclinic Spring Boot application, which was used as an example in this article, starts up pretty quickly (in 3-5 seconds) on M1 Macs when using system Java or Docker. Finally, cross-building can be done in the opposite direction, that is, container images for ARM-based systems can be prepared on x86 machines, and you can create multi-platform images as well.