Speed up your java application Docker images build with BuildKit!

While admitting all the goodness it offers, Java developers still args that containers added a new layer of abstraction and made local development a bit more difficult: Writing a Dockerfile, setting up Docker daemon, waiting for builds to complete, fixing errors if any, then finally get our new unit of deployment, container images! This adds some latency when using Docker in development cycle, especially the build time, maven/gradle caching issues, filesystem change detection,...!

In this post, we'll see how Buildkit can help us to speed up the build process for your java application images.

What is BuildKit?

BuildKit is a new project under the Moby umbrella for building and packaging software using containers. it's a builder toolkit for converting source code to build artifacts in an efficient, expressive and repeatable manner.

Buildkit offers a number of great features compared to the old builder integrated in docker build command! For example, let's consider the following Dockerfile:

FROM maven:3.6.1-jdk-11-slim AS build  
RUN mvn clean package

Modifying the second line always invalidate maven cache due to false dependency, which exposes inefficient caching issue. BuildKit solves this limitation by introducing the concurrent build graph solver, which can run build steps in parallel and optimize out commands that don’t have an impact on the final result. Additionally, Buildkit tracks only the updates made to the files between repeated build invocations that optimize the access to the local source files. Thus, there is no need to wait for local files to be read or uploaded before the work can begin.

Buildkit is meant to become the next generation backend implementation for docker build command.

Using BuildKit

Since Docker 18.09, BuildKit is an opt-in feature that can be enabled by setting "features": {"buildkit": true} into the daemon.json file, or with setting the environment variable DOCKER_BUILDKIT=1 before running docker build.

If you're using some older versions (18.06 or recent), you've to add "experimental": true into the daemon.json file and set the environment variable DOCKER_BUILDKIT=1 before the build.

Mounting Maven local repository

Usually, we mount maven local repository (.m2) into our container to improve the build process using docker volumes. This technique has the drawback of polluting the shared folder with some root owned files!

Buildkit, extends the RUN instruction in Dockerfile to add the --mount option as part of the Dockerfile frontend experimental syntaxes. A special type is the cache mount type, that could be of great use while building your java applications with your favorite build tool.

Consider the following Dockerfile:

# syntax=docker/dockerfile:experimental
FROM maven:3.6.1-jdk-11-slim AS build  
RUN --mount=type=cache,target=/root/.m2 mvn -f /usr/src/app/pom.xml clean package

FROM openjdk:11-slim
COPY --from=build /usr/src/app/target/*.jar app.jar  
ENTRYPOINT ["java", "-XshowSettings:vm", "-XX:NativeMemoryTracking=summary", "-jar", "app.jar"]

We're mounting the /root/.m2 while running the command that can be shared between multiple builds as it remains in Docker host machine. It can be removed anytime by simply running docker builder prune --filter type=exec.cachemount command.

Ressources & Further reading: