Effortless Java Containers: Building Docker Images Without Dockerfiles Using Jib

The shift to containerized applications, often with Docker, has become a standard in modern software development. However, the traditional approach of writing a Dockerfile and running a docker build can be inefficient, insecure, and inconsistent for Java developers. Enter Jib, an open-source Java tool from Google that builds optimized Docker and OCI images for your Java applications without requiring a Docker daemon or even writing a Dockerfile.

This article explores what Jib is, why it's a game-changer for Java, and how to use it with Maven and Gradle to create lean, secure, and reproducible container images.


The Problem with Traditional Docker Builds for Java

The typical Dockerfile for a Java application looks something like this:

FROM openjdk:17-jdk-slim
COPY target/my-app-1.0.0.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

This approach has several drawbacks:

  1. Inefficiency: Every code change rebuilds the entire JAR file, and Docker sees it as a new file, invalidating the cache layer and forcing a rebuild from the COPY instruction onward.
  2. Non-Reproducible Builds: The build is not entirely repeatable. The base image (openjdk:17-jdk-slim) might change between builds, and the build context can contain inconsistent files.
  3. Complexity for Layering: Optimizing the image to leverage Docker's layer caching for dependencies requires a multi-stage, complex Dockerfile.
  4. Security: It often requires running the container as the root user by default and includes a full JDK for running a JAR, which is unnecessary.

What is Jib?

Jib is a fast and simple container image builder that handles two main tasks:

  • Packages your application into a container image.
  • Pushes the image to a container registry.

Its most significant feature is that it is Docker daemon-less. You don't need Docker installed on your machine to create a container image. This is ideal for CI/CD environments and improves security and consistency.


Core Principles and Advantages of Jib

1. Intelligent Layer Caching

Jib separates your application into distinct, logical layers on the filesystem:

  • Dependencies Layer: (Cached) Contains all files from lib/. This layer is rebuilt only when pom.xml or build.gradle changes.
  • Resources Layer: (Cached) Contains static resources. Rebuilt when files in src/main/resources change.
  • Classes Layer: (Volatile) Contains your compiled classes. Rebuilt when Java source code changes.

This means a code change only rebuilds and pushes the tiny "classes" layer, making builds incredibly fast after the first run.

2. Reproducible Builds

Jib builds are reproducible by default. If the input (source code, dependencies, resources) does not change, the resulting image digest will be identical every time. This is crucial for debugging and reliable deployments.

3. Security by Default

  • Distroless Base Images: Jib encourages the use of Google's "distroless" base images, which contain only your application and its runtime dependencies, dramatically reducing the attack surface.
  • Non-Root User: Jib can automatically configure the image to run as a non-root user, a critical security best practice.

How to Use Jib

Jib provides plugins for both Maven and Gradle. The configuration is straightforward and lives directly in your build script.

Maven Configuration

Add the following to your pom.xml:

<project>
<!-- ... -->
<build>
<plugins>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<to>
<image>my-registry.example.com/my-team/my-app:${project.version}</image>
</to>
<container>
<ports>
<port>8080</port>
</ports>
<user>1000</user> <!-- Run as non-root user -->
</container>
</configuration>
</plugin>
</plugins>
</build>
</project>

Gradle Configuration

Add the following to your build.gradle:

plugins {
id 'com.google.cloud.tools.jib' version '3.4.0'
}
jib {
to {
image = "my-registry.example.com/my-team/my-app:${version}"
}
container {
ports = ['8080']
user = '1000' // Run as non-root user
}
}

Common Jib Commands

Building to a Local Docker Daemon

If you have Docker running and want to test the image locally:

Maven:

./mvnw compile jib:dockerBuild

Gradle:

./gradlew jibDockerBuild

Building and Pushing Directly to a Registry

This is the most powerful feature. It builds and pushes the image without using Docker.

Maven:

./mvnw compile jib:build

Gradle:

./gradlew jib

Prerequisite: You must be authenticated to your container registry (e.g., Docker Hub, Google Container Registry, Amazon ECR). Jib can use credential helpers like docker-credential-gcloud or standard ~/.docker/config.json.

Building with a Specific Base Image

To use a more secure distroless base image instead of the default one:

Maven:

<configuration>
<from>
<image>gcr.io/distroless/java17-debian11</image>
</from>
<to>...</to>
</configuration>

Gradle:

jib {
from {
image = 'gcr.io/distroless/java17-debian11'
}
to { ... }
}

Advanced Example: Spring Boot Application with Jib

Here’s a complete example for a typical Spring Boot application.

Maven pom.xml snippet:

<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<from>
<!-- Use a small JRE base image -->
<image>eclipse-temurin:17-jre-alpine</image>
</from>
<to>
<image>docker.io/my-dockerhub-username/my-spring-boot-app:${project.version}</image>
<auth>
<username>${env.DOCKER_USERNAME}</username>
<password>${env.DOCKER_PASSWORD}</password>
</auth>
</to>
<container>
<ports>
<port>8080</port>
</ports>
<environment>
<SPRING_PROFILES_ACTIVE>prod</SPRING_PROFILES_ACTIVE>
</environment>
<creationTime>USE_CURRENT_TIMESTAMP</creationTime>
</container>
</configuration>
</plugin>

To build and push this to Docker Hub, you would run:

export DOCKER_USERNAME=your-username
export DOCKER_PASSWORD=your-personal-access-token
./mvnw compile jib:build

Best Practices

  1. Use JRE, not JDK: Your application only needs a Java Runtime Environment (JRE) to run, not the full JDK. Use base images like eclipse-temurin:17-jre-alpine or gcr.io/distroless/java17.
  2. Leverage Layer Caching: Structure your project so that dependencies are not invalidated by frequent code changes.
  3. Run as Non-Root: Always configure the <user> setting for security.
  4. Parameterize Credentials: Never hardcode registry credentials in your build file. Use environment variables or secret management in your CI/CD system.
  5. Combine with Buildpacks: For even more simplicity, explore Spring Boot 2.3+'s built-in support for Cloud Native Buildpacks (./mvnw spring-boot:build-image), which shares similar goals with Jib.

Conclusion

Jib transforms the Java containerization experience. By integrating directly with your build tool and understanding the structure of a Java application, it produces secure, efficient, and reproducible images faster and more reliably than traditional Docker builds. Its daemon-less architecture not only simplifies CI/CD pipelines but also enhances their security. For any Java team embracing containers, Jib is an indispensable tool that eliminates friction and enforces best practices by default.


Further Reading: Explore the Jib project page on GitHub for more advanced configurations, including building multi-architecture images and customizing the file structure.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper