GitHub - chrisbensen/tiny-java-containers · GitHub
Skip to content

chrisbensen/tiny-java-containers

 
 

Folders and files

Repository files navigation

tiny-java-containers

An example showing how a simple Java application can be compiled to produce a very small Docker container image.

The smallest container images contains just an executable. But since there's nothing in the container image except the executable, including no libc or other shared libraries, the executable has to be fully statically linked with all needed libraries and resources.

To support static linking libc, GraalVM Native Image supports using the "lightweight, fast, simple, free" musl libc implementation.

NOTE: GraalVM Native Image also supports dynamically linked and "mostly static" executables not described here.

Prerequisites

You'll need GraalVM Native Image installed. This code was last tested with version 22.1. You'll also need Docker installed and running. It should work fine with podman but it has not been tested.

These instructions have only been tested on Linux amd64.

Setup and Build

Clone this Git repo and in your shell type the following to download and configure the MUSL toolchain.

% source setup-musl.sh

Next compile a simple single Java class Hello World application with javac, compile the generated .class file into a fully statically linked native Linux executable, compress the executable with upx, and package both the static executable and the compressed executable into scratch Docker container images using the provided script:

% ./build-hello.sh

The Executables

Running either of the executable you can see they are functionally equivalent. They just print "Hello World". But there are a few points worth noting:

  1. The executable generated by GraalVM Native Image using the --static --libc=musl options is a fully self-contained executable which can be confirmed by examining it with ldd:

% ldd hello.static

should result in:

	not a dynamic executable

Unfortunately upx compression renders ldd unable to list the shared libraries of an executable, but since we compressed the statically linked executable we can be confident it is also statically linked.

  1. Both executables are the result of compiling a Java bytecode application into native machine code. The uncompressed executable is only 5.2MB! There's no JVM, no jars, no JIT compiler and none of the overhead it imposes. Both start extremely fast as there is effectively no startup cost.
  2. The upx compressed executable is about 60% smaller, 1.5MB vs. 5.2MB! With upx the application self-extracts but so quickly as to have minimal impact on startup time.

Container Images

The sizes of the scratch-based container images are in proportion to the executables.

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello               upx                 935e5e3549e6        1 second ago        1.51MB
hello               static              4d41b253b760        4 seconds ago       5.45MB

These are tiny container images and yet they contain fully functional and deployable (although fairly useless 😉) applications.

The Dockerfiles that generated them simply copy the executable into the container image and set the executable as the ENTRYPOINT. E.g.,

FROM scratch
COPY hello.upx /
ENTRYPOINT ["/hello.upx"]

Running them is straightforward:

% docker run --rm hello:static

Hello World

% docker run --rm hello:upx

Hello World

Wrapping Up

There you have it. A fully functional, albeit minimal, Java application compiled into a native Linux executable and packaged into a scratch container image thanks to GraalVM Native Image's support for fully static linking with the musl libc.

To explore other linking options compatible with other base container images check out Static and Mostly Static Images in the GraalVM docs.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

Contributors

Languages

  • Shell 88.4%
  • Java 11.6%