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:
- 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
COPYinstruction onward. - 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. - Complexity for Layering: Optimizing the image to leverage Docker's layer caching for dependencies requires a multi-stage, complex Dockerfile.
- Security: It often requires running the container as the
rootuser 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 whenpom.xmlorbuild.gradlechanges. - Resources Layer: (Cached) Contains static resources. Rebuilt when files in
src/main/resourceschange. - 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
- 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-alpineorgcr.io/distroless/java17. - Leverage Layer Caching: Structure your project so that dependencies are not invalidated by frequent code changes.
- Run as Non-Root: Always configure the
<user>setting for security. - Parameterize Credentials: Never hardcode registry credentials in your build file. Use environment variables or secret management in your CI/CD system.
- 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.