Java in Docker on Apple Silicon

Java in Docker on Apple Silicon


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.

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

Liberica JDK Installer

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.

Install Docker:

Docker Installer

Launch Docker Desktop, it will ask for privileges once to finish the setup.

Docker Alert

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):

Docker GUI

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:

Petclinic

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

Author image

Dmitry Chuyko

Senior Performance Architect at BellSoft

BellSoft LTD [email protected] BellSoft LTD logo Liberica Committed to Freedom 199 Obvodnogo Kanala Emb. 190020 St. Petersburg RU +7 812-336-35-67 BellSoft LTD 199 Obvodnogo Kanala Emb. 190020 St. Petersburg RU +7 812-336-35-67 BellSoft LTD 111 North Market Street, Suite 300 CA 95113 San Jose US +1 702 213-59-59