Google Jib And Why You Will Love It
Google Jib is a great tool for building Docker images from Maven and Gradle applications – it integrates easily with both and it’s super simple to set up and use. Therefore, it is the perfect companion on anyone’s journey to assemble an application from containerized microservices.
In the context of microservices, you’ll often find yourself building Docker images or, ideally, operating some tool that does it for you. By far the best build tool I’ve encountered so far is Google Jib – it’s very convenient to use, you don’t need a Docker daemon running locally (although you can pass the build to a local Docker daemon), and its smart arranging of Docker filesystem layers ensures only the layer containing modified source code has to be rebuilt, thus speeding up the overall build-and-push process. In this short blog post, we’ll take a look at how to use Google Jib in a Maven project to build and push a Docker image.
POM Setup
You can find this post’s sample application in this GitHub repository. Its source code doesn’t do much, rather, the interesting stuff happens in the build
section of the project’s POM file:
<!-- [truncated] -->
<build>
<plugins>
<!-- [truncated] -->
<plugin>
<!-- (1) -->
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>${google-jib-version}</version>
<configuration>
<!-- (2) -->
<from>
<image>adoptopenjdk:14.0.2_8-jre-hotspot-bionic@sha256:c8855[truncated]</image>
</from>
<!-- (3) -->
<to>
<image>docker.io/antsinmyey3sjohnson/jib-demo</image>
<tags>${project.version}</tags>
</to>
<!-- (4) -->
<container>
<jvmFlags>
<jvmFlag>-Xmx${java-xmx}</jvmFlag>
<jvmFlag>-Xms${java-xms}</jvmFlag>
</jvmFlags>
</container>
</configuration>
<!-- (5) -->
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- [truncated] -->
There are a few noteworthy sections in here:
- Maven coordinates for the jib-maven-plugin we use to pull in Jib functionality to the Maven build. The version was specified to be 2.5.2 in the POM’s
<properties>
section. - At the time of writing, Jib will by default attempt to use a Java 11 base image (a very slim, distro-less image maintained by Google in scope of their distro-less project), and it’s smart enough to recognize this wouldn’t work here since
java.version
was set to 14 a bit further up in the POM file. Therefore, we specify our own base image. This base image provides specific versions for different platforms, so it’s important to specify which one we want by using that version’s digest (the one provided here points to the linux/amd46 image version). The build will still work if the digest is removed because Jib will evaluate the current platform and search for a matching candidate, but this would destroy build reproducibility since a different image will be pulled in if you happen to run the build on a different platform. - We push our image to a public DockerHub registry, using the current project version as the image tag to be used. Note that authentication details have to be present on the system running the build for this to work.
- Tell Jib to pass two JVM flags to the
java -jar
command that will eventually run the application inside the container. Bothjava-xmx
andjava-xms
have been specified in the POM’s<properties>
section, and both can be overridden on the command line (see below for an example). - Finally, we include the
jib:build
goal in thepackage
standard Maven lifecycle phase.
Building And Running The Image
With this jib-maven-plugin configuration in place, we can trigger the image build like so:
$ mvn package -Djava-xmx=128m -Djava-xms=128m
This will run the standard Maven lifecycle phases up to the package phase, in scope of which the jib:build goal will be invoked to build our Docker image. The command also specifies values for both JVM flags, overwriting the default ones provided in the POM file itself.
As soon as the Jib build goal is invoked, it will print some output to the console. Conveniently, this also reveals the container entry point created based on our plugin configuration and the given command-line parameters:
Container entry point set to [java, -Xmx128m, -Xms128m, -cp, /app/resources:/app/classes:/app/libs/*, io.github.antsinmyey3sjohnson.jibdemo.JibDemoApplication]
Finally, we can, of course, run our Docker image, see if everything works and then perform a clean-up:
$ docker run -d --name jib-demo --rm -p 8080:8080 antsinmyey3sjohnson/jib-demo:0.0.1-SNAPSHOT
b8a6f3f40fab5d64889c905e0610eb695374ad0b95ad53739e55a55a15ede800
$ curl localhost:8080
{"message":"Hi, my Docker image was built using Google Jib!"}
$ docker stop b8a6
A Note On Reproducibility
The docker images
command reveals a very interesting detail about the newly built image:
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
antsinmyey3sjohnson/jib-demo 0.0.1-SNAPSHOT 232c366c0471 50 years ago 285MB
Yup, that’s right – apparently, this image was created 50 years ago. Now, I wasn’t around back then so I don’t have proof, but I’m pretty sure Docker wasn’t yet a thing at that time. So, what’s the reason for this odd timestamp?
Jib sets the creation timestamp to the Unix epoch (00:00:00 UTC on 1 January 1970) by default (see here). Although this can be overridden, doing so is discouraged for reasons of reproducibility. This is explained in more detail in this section of the Jib FAQs, and I provide a short summary here:
The image’s creation time is one of the information elements used to calculate the image’s digest, which Docker tooling uses to uniquely identify the image. This means different creation times will lead to different images. So, for example, if you use the current timestamp in your builds, each subsequent build will yield a different digest, and each Docker-related component down the line (for example, the container orchestration framework listening for new images) will treat the resulting image as a new image while the stuff that would, in fact, justify a new image – modified source code, new configuration, etc. – may not have changed at all, thus destroying build reproducibility.
Wrap-Up
Google Jib makes it incredibly easy to dockerize a Maven project, and it also has support for Gradle. Both are built around Jib Core and the Jib CLI, so you can use those if you want to add some functionality that’s not yet included. In other posts on this blog, you’ll encounter Google Jib pretty often – it’s used whenever the task is to build a container image from a Maven project.