/ DOCKER, DESIGN

Composition over inheritance applied to Docker

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:

  1. The Dockerfile is updated with the new parent image
  2. A new image is built
  3. The resulting image is made available on a Docker registry
  4. 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.

Docker and containerization technologies have been around only for some time. It will probably take some time - as well as some trials and errors - to understand how to make the most of it in one’s own context. This post exposes one of the many options available.

Nicolas Fränkel

Nicolas Fränkel

Nicolas Fränkel is a Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Currently working for Hazelcast. Also double as a teacher in universities and higher education schools, a trainer and triples as a book author.

Read More