Java in Docker on Apple Silicon
Published April 9, 2021
As you know Apple has begun the transition from Intel x86_64 processors to ARM64-based Apple silicon chips in Mac computers. 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.
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 (read more here), just go and 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? On 1st April a new tech preview of Docker Desktop was released. Improvements are ongoing, but it is already easy to use. And here Rosetta is still a must. Detailed instructions and download links can be found here.
Update: As of April 15, Docker Desktop for M1 Macs is fully available as a GA release.
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.
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. So, of course, you cannot only clone and build your application, but develop it as usual. Some errors still occur when Docker performs normal operations, but these errors are really rare. 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.