Posts

Java in Docker on Apple Silicon

Apr 9, 2021
Dmitry Chuyko
7.3

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.

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.

Subcribe to our newsletter

figure

Read the industry news, receive solutions to your problems, and find the ways to save money.

Further reading