This post is neither a recommendation, nor even a suggestion. It’s just me toying with an idea: Implement it at your own risk!

The main benefit of Docker containers is that they are self-contained. For developers, that means one just needs to inherit from the desired Docker image that contains the necessary required dependencies, and presto, one can build one’s application deliver it to production. Most of the times, the process is pretty straightforward. Containerization allows scaling on unprecedented scale.

However, the downside of this self-containedness is that updating the parent image(s) becomes a nightmare. The process is the following:

The Dockerfile is updated with the new parent image A new image is built The resulting image is made available on a Docker registry Finally, it’s pulled from said registry on the production machine, and started

While that might be acceptable for a single image once in a while, it’s definitely not feasible with the update frequency increasing, as well as the number of containerized applications involved.

Now, let’s consider the existing tradeoff of self-containedness vs flexibility, and relax the constraint. Instead of inheriting from the image, one could expose the folder that contains the required dependencies: it’s exactly the same OOP principle of Favor composition over inheritance! With that design, one just needs to replace the composed image to update the required binaries.

As an example, I’ll create a simple Java application incorporating this design. I assume a Maven project that generates an executable JAR. Here’s the relevant Dockerfile :

Dockerfile FROM maven:3.6.0-alpine as build COPY src src COPY pom.xml . RUN mvn package FROM alpine:3.8 COPY --from=build target/composition-example-1.0-SNAPSHOT.jar . ENTRYPOINT ["sh", "-c", "/usr/bin/java -jar composition-example-1.0-SNAPSHOT.jar"]

It’s a multi-stage build that first builds the Maven project, and then runs it via /usr/bin/java .

Note that standard Dockerfiles would inherit from a base JRE image, such as openjdk:8-jre-alpine . Here, the second stage inherits from the base alpine image, there’s no java executable available. Hence, running the built Docker image will fail:

$ docker build -t compose-this . $ docker run compose-this -jar : line 1: java: not found

To fix that, let’s create a Docker image that exposes its java executable in a volume:

Dockerfile FROM openjdk:8-jre-alpine VOLUME /usr/bin VOLUME /usr/lib

While java is located in /usr/bin , it’s just a symlink pointing to /usr/lib/jvm/default-jvm/jre/bin/java . Hence, the /usr/lib folder needs to be exposed as well.

Build and run that image:

$ docker build -t myjava:8 . $ docker run --name java myjava:8

At that point, it becomes possible to bind the volumes from the java container to new containers running the compose-this image:

docker run - it -- volumes - from java compose - this

Not only it’s now much easier to update the dependent JRE, an added benefit is that the application image has been drastically reduced compared to its standalone image: it contains only the JAR.

A question could arise: what would be the difference between this setup and the uncontainerized one, with plain JARs and a shared JRE on the filesystem? This allows orchestration i.e. Kubernetes. While the same could be achieved with configuration management tools e.g. Puppet or Chef, Kubernetes is becoming the de facto platform to deploy to.