FROM gcr.io/distroless/java:11
COPY target/mvc-demo-bootable.jar /app/main.jar
WORKDIR /app
CMD ["main.jar"]
02 June 2021
Containerization of Java applications is as easy as copying the application's Jar file into a JRE equipped base image. But choosing the right base image can be hard and have a big impact on performance, effectiveness of resource utilization, security and costs. This Blog Post discusses the trend to Distroless base images.
If you’re looking around for an appropriate base image to package your application, you may stumble into the term distroless image. This term was coined by Google, which describes it as
Distroless images contain only your application and its runtime dependencies. They do not contain package managers, shells or any other programs you would expect to find in a standard Linux distribution.
The described approach is best practice and basically means to restrict what’s going into your runtime container to precisely what’s necessary for the application.
Although, distroless images are rather a general principal, Google is still the major supplier of such images. Google provides images for different languages like
Java
Python
JavaScript/NodeJS
Golang
Technically, the distroless base images contain mainly the language platform components, i.e. in case of Java the JDK. Because the JDK is dynamically linked to system libraries like GlibC, these libraries are also contained in the image.
The list of supported languages even includes Go. This may be surprising
a little bit at the first glance, because Go applications are usually
linked statically and can be packaged into images |
Before discussing the benefits of applications packaged with distroless images, let’s have a look at the containerization of a sample Java application. I’ll use the Jakarta MVC demo application of one of the last Blog posts, which is packaged as Wildfly Bootable JAR, i.e. as a Single Fat Jar.
The following Dockerfile
just copies the application’s Jar file into the image,
which is based on Google’s distroless image for Java 11:
FROM gcr.io/distroless/java:11
COPY target/mvc-demo-bootable.jar /app/main.jar
WORKDIR /app
CMD ["main.jar"]
Because the java
command is defined as entrypoint of the image, the
application’s Jar can be provided as command.
The regular docker build
command creates the image:
$ docker build -t mvc-app .
Sending build context to Docker daemon 204.7MB
Step 1/4 : FROM gcr.io/distroless/java:11
---> 6395a77cb03c
Step 2/4 : COPY target/mvc-demo-bootable.jar /app/main.jar
---> 2209115d4185
Step 3/4 : WORKDIR /app
---> Running in 5faa7537ec9b
Removing intermediate container 5faa7537ec9b
---> a81720f1f285
Step 4/4 : CMD ["main.jar"]
---> Running in 944c9a473721
Removing intermediate container 944c9a473721
---> d21a6c599d58
Successfully built d21a6c599d58
Successfully tagged mvc-app:latest
This containerized application can be executed on Linux by:
$ docker run --sysctl net.ipv4.ip_forward=1 --network=host -p 8080:8080 --rm -it mvc-app:latest
WARNING: Published ports are discarded when using host network mode
05:26:38,437 INFO [org.wildfly.jar] (main) WFLYJAR0007: Installed server and application in /tmp/wildfly-bootable-server5397933122926229342, took 932ms
05:26:38,684 INFO [org.wildfly.jar] (main) WFLYJAR0008: Server options: [--read-only-server-config=standalone.xml]
05:26:38,806 INFO [org.jboss.msc] (main) JBoss MSC version 1.4.12.Final
05:26:38,814 INFO [org.jboss.threads] (main) JBoss Threads version 2.4.0.Final
05:26:38,979 INFO [org.jboss.as] (MSC service thread 1-3) WFLYSRV0049: WildFly Full 22.0.1.Final (WildFly Core 14.0.1.Final) starting
05:26:39,967 INFO [org.jboss.as.jaxrs] (ServerService Thread Pool -- 15) WFLYRS0016: RESTEasy version 3.14.0.Final
05:26:39,988 INFO [org.wildfly.extension.undertow] (MSC service thread 1-6) WFLYUT0003: Undertow 2.2.4.Final starting
...
05:26:43,418 INFO [org.jboss.as.server] (Controller Boot Thread) WFLYSRV0010: Deployed "mvc-demo.war" (runtime-name : "ROOT.war")
05:26:43,449 INFO [org.jboss.as.server] (Controller Boot Thread) WFLYSRV0212: Resuming server
05:26:43,451 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: WildFly Full 22.0.1.Final (WildFly Core 14.0.1.Final) started in 4761ms - Started 144 of 149 services (23 services are lazy, passive or on-demand)
Beside the simple and straightforward image build, the generated images are surprisingly small compared to Java application images built on top of distribution based images:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mvc-app latest d21a6c599d58 4 minutes ago 265MB
...
From my experience, the size of application images based on a Linux distribution is about 200 MB larger - the only exception of this are Alpine Linux based images, but these have other drawbacks…
Distroless best resembles the origins of application containers, i.e. packaging applications with its required runtime components in order to
isolate applications in the best possible way
allow resource management like CPU and memory quotas on a per application basis
At the same time it refutes the widespread misconception that containers are a replacement for VMs.
The main attribute of distroless image applications is their small size. And size actually matters because of:
Images are copied, transmitted and launched by fleet managers like Kubernetes. In addition, fitting more containers into one machine means less machine spawns.
Improved security by minimizing the attack surface, because everything in your container, e.g. shells, not used by your application can still be used by attackers.
Fitting more containers into one machine (AKA worker node in Kubernetes) reduces the bill from your cloud provider.
There are situations, in which a shell access to an application’s container may
be convenient. For such situations Google offers a debug version of their
distroless containers, which includes a busybox
in addition. But usually
containerized applications should go without a shell. This may be not possible
for legacy applications brought to the cloud applying a Lift-and-Shift
approach. But cloud-native applications should be designed and implemented in
a way, that shells and other components of a Linux distribution are not required.