Project Portola: Porting java to Alpine Linux!

There are considerable benefits of using, building and deploying small container images (small attack surface, faster updates, less network costs ...), and when it comes to lightweight distributions, Alpine Linux is by far the favorite choice.

However, Java developers might find issues while running their containerized applications on alpine as stated on the OpenJdk 12 page:

The Alpine Linux build previously available on this page was removed as of JDK 12.0.1 GA. It’s not production-ready because it hasn’t been tested thoroughly enough to be considered a GA build. Please use the early-access JDK 13 Alpine Linux build in its place.

Alpine Linux

Alpine is a security focused, general purpose, lightweight linux distribution. The official alpine 3.10 docker image has a size of ~3M, compared to 28M for the ubuntu eoan Image, which makes it the favorite distro to use as the base image, where small size is highly desirable. Alpine is built around musl libc and busy busybox.

Busybox is, as stated by its creator Bruce Perens , "The Swiss Army Knife of Embedded Linux". It combines tiny versions of many common UNIX utilities into a single small executable. it provides the core functionalities for dozens of standard programs, such as awk, cp, grep, gzip, sh and top.

musl libc is an implementation of libc, the standard library for the C programming language. This is basically the standardized interface between the kernel and userland, doing such things string handling, mathematical computations, input/output processing, memory management, and several other operating system services.

However, you won't be surprised to know that the most widely used C library on all major Linux distributions is the GNU C Library, referred to as glibc. Although both are implementation of the same abstraction library, they expose slightly different APIs. This comparison chart gives a very detailed picture of the differences between the libc implementations available for Linux.

The problem is when applications are compiled, they are compiled against a specific libc for the most part and if you want to use them with another libc you have to recompile them.

Java & Alpine Linux

If you take a look at the OpenJDK repository, you notice the presence of many C/C++ code (around 19%)! The JVM has been already ported to glibc, but in order to ensure a transparent run of the JVM (and its C/C++ code) on musl, an effort should be made to port the JVM to it as well.

This is the goal of OpenJDK [“Project Portola”](https://openjdk.java.net/projects/portola/, that aims to provide a port of the JDK to Alpine Linux, and in-particular the musl C library. An early access is available as part of java 13. It still in experimental status, needs more hardening and no GA is available for the moment.

It is worthwhile to mention that IcedTea project provides stable Alpine OpenJDK builds for versions 7 and 8. Azul's Zulu OpenJDK offers a compelling alternative as well. It supports Java 11 & 12 and it is, as far as I know, the only certified OpenJDK build on alpine. It is free, open source and docker ready.

Also, you probably already used (or using right now) Alpine images provided by AdoptOpenJDK. Those images are not using Portola or any other port of the jvm, they're simply adding glibc package on top of Alpine.

Portola in action!

The Dockerfile below uses multi-stage builds feature, where in the build stage we download the openjdk Alpine linux jdk build and calls Project Jigsaw’s jlink, which offers the ability to produce a minimal JRE based on the modules our application requires. The final stage simply runs our netty application using the custom JRE:

FROM alpine:3.10 AS build
ENV JAVA_HOME /opt/jdk
ENV PATH $JAVA_HOME/bin:$PATH
ADD https://download.java.net/java/early_access/alpine/27/binaries/openjdk-13-ea+27_linux-x64-musl_bin.tar.gz $JAVA_HOME/openjdk.tar.gz
RUN tar --extract --file $JAVA_HOME/openjdk.tar.gz --directory "$JAVA_HOME" --strip-components 1; \
	rm $JAVA_HOME/openjdk.tar.gz;
# jdeps can help identify which modules an application uses
RUN ["jlink", "--compress=2", \
     "--module-path", "/opt/jdk/jmods/", \
     "--add-modules", "java.base,java.logging,java.naming,java.xml,jdk.sctp,jdk.unsupported", \
     "--no-header-files", "--no-man-pages", \
     "--output", "/netty-runtime"]

FROM alpine:3.10
COPY --from=build  /netty-runtime /opt/jdk
ENV PATH=$PATH:/opt/jdk/bin
COPY target/netty-example-1.0-SNAPSHOT.jar /opt/app/
CMD ["java", "-showversion", "-jar", "/opt/app/netty-example-1.0-SNAPSHOT.jar"]

Building this Dockerfile results in a very lightweight image, about 50MB:

# docker images                                                                    
dc/portola      latest    90898f852920      50.8MB
....

The Protola project is very promising and you can participate in moving forward this effort by testing, reporting bugs and fixing issues.

Ressources and further reading