Jib is a great tool which enables to build container images and push them either to a remote registry - without any daemon, even docker one!, a local filesystem - as a tar.gz, or a plain local docker image. It comes with a great Maven binding which enables to set up it as a simple maven plugin for any java application. In its defaults it comes with a convention for layers intended to be reproducible and optimized - trying to limit the number of layers downloaded once you already got the image once.

If you are building a final service it is an awesome solution, but if, like me, you are building a generic server which can be extended by the user, containers can become a nightmare.

The typically usages are:

  • Enable the user to customize the server with custom configuration files,
  • Enable the user to add custom logic - through .jar. It is likely relying on your IoC - CDI/Spring - or the plain old ServiceLoader of JavaSE.

For custom configuration files, it is generally simple to mount a file in the container if planned and just read the configuration from there. However for the binaries extensibility it can be harder because in several cases, the JVM will fail if the folder/jar does not exist.

Indeed an option is to use a custom classloader but it changes the behavior of your application - in particular since Java (Platform) Module System - is there, so better to just try to enrich the classpath.

Since recent versions, Jib got a new option called extraClasspath which enables to add entries into the classpath which are not in the project or project dependencies. This can be neat to add an exploded zip or so.

Thanks to the JVM implementation, it also opens the door to extensibility. The classpath option supports wildcard patterns so you can set a folder and append * at the end of the path to ask the JVM to take all the jars in this folder.

Concretely here what it looks like for Jib - but it can be reused with a plain docker image:

<plugin>
  <groupId>com.google.cloud.tools</groupId>
  <artifactId>jib-maven-plugin</artifactId>
  <version>${jib.version}</version>
  <executions>
    <execution>
      <id>build</id>
      <phase>none</phase>
      <configuration>
        <from>
          <image>openjdk:8-slim</image>
        </from>
        <to>
          <image>rmannibucau/demoapp:${project.version}</image>
        </to>
        <container>
          <extraClasspath>/opt/rmannibucau/custom/*</extraClasspath>
          <mainClass>com.github.rmannibucau.app.MyMain</mainClass>
        </container>
      </configuration>
    </execution>
  </executions>
</plugin>

With this setup you can run the following command to create the docker image defined by this project:

mvn clean install jib:dockerBuild@build

Tip: the exact same tip works for the Gradle flavor of Jib.

Then you can simply mount a volume with jars in /opt/rmannibucau/custom to enrich the application classpath:

docker run \
  -v /local/app/enrichment/:/opt/rmannibucau/custom/ \
  rmannibucau/demoapp:1.0.0

This will mount the local folder in the custom container folder and let the JVM expand the extraClasspath option to replace it by the list of jar in the local enrichment/ folder activating your extensions :).

In my opinion, most of public images should use that solution because you never fully control the user usages and requirements in terms of deployment - and Docker does not fully help there. One simple example can be that you application provides log4j2 without JSON and YAML support, then with this solution, the user can mount jackson libraries and its configuration through the custom folder and use that feature. Containers are bringing up constraints but also solutions to make them run away - otherwise it would be impossible to use. Let's use them ;).

From the same author:

In the same category: