Jib vs Docker Build for Java Applications

Introduction

When containerizing Java applications, developers have two primary approaches: traditional Docker builds using Dockerfiles and Jib, a container-building tool from Google that doesn't require a Docker daemon. This comparison explores both technologies, their strengths, weaknesses, and ideal use cases.

Core Concepts

1. Docker Build with Dockerfile

# Traditional Dockerfile for Spring Boot
FROM eclipse-temurin:21-jre-jammy
WORKDIR /app
# Create non-root user for security
RUN groupadd -r spring && useradd -r -g spring spring
USER spring
# Copy JAR file
COPY target/myapp-*.jar app.jar
# Configure JVM options
ENV JAVA_OPTS="-Xmx512m -Xms256m -XX:+UseG1GC"
ENV SPRING_PROFILES_ACTIVE="default"
# Expose port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# Entry point
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

2. Jib Configuration

<!-- Maven configuration for Jib -->
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<from>
<image>eclipse-temurin:21-jre-jammy</image>
</from>
<to>
<image>myregistry/myapp:${project.version}</image>
<tags>
<tag>latest</tag>
</tags>
</to>
<container>
<entrypoint>
<arg>java</arg>
<arg>-Xmx512m</arg>
<arg>-Xms256m</arg>
<arg>-XX:+UseG1GC</arg>
<arg>-jar</arg>
<arg>/app/myapp.jar</arg>
</entrypoint>
<ports>
<port>8080</port>
</ports>
<environment>
<SPRING_PROFILES_ACTIVE>default</SPRING_PROFILES_ACTIVE>
</environment>
<creationTime>USE_CURRENT_TIMESTAMP</creationTime>
<user>1000</user>
<format>OCI</format>
</container>
</configuration>
</plugin>
// Gradle configuration for Jib
plugins {
id("com.google.cloud.tools.jib") version "3.4.0"
}
jib {
from {
image = "eclipse-temurin:21-jre-jammy"
}
to {
image = "myregistry/myapp:${project.version}"
tags = setOf("latest")
}
container {
entrypoint = listOf(
"java", "-Xmx512m", "-Xms256m", "-XX:+UseG1GC", 
"-jar", "/app/myapp.jar"
)
ports = listOf("8080")
environment = mapOf(
"SPRING_PROFILES_ACTIVE" to "default"
)
creationTime = "USE_CURRENT_TIMESTAMP"
user = "1000"
format = "OCI"
}
}

Build Process Comparison

1. Docker Build Process

public class DockerBuildProcess {
public void traditionalDockerBuild() {
// Manual steps required:
// 1. Build JAR: mvn clean package
// 2. Write Dockerfile
// 3. Build image: docker build -t myapp .
// 4. Push to registry: docker push myregistry/myapp
// Issues:
// - Multi-stage builds needed for optimization
// - Requires Docker daemon
// - Reproducibility challenges
// - Larger image sizes without optimization
}
public void optimizedDockerBuild() {
// Multi-stage Dockerfile example
String dockerfile = """
# Build stage
FROM eclipse-temurin:21-jdk-jammy as builder
WORKDIR /workspace
COPY pom.xml .
COPY src src
RUN ./mvnw package -DskipTests
# Runtime stage
FROM eclipse-temurin:21-jre-jammy
RUN addgroup -S spring && adduser -S spring -G spring
USER spring
COPY --from=builder /workspace/target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
""";
}
}

2. Jib Build Process

public class JibBuildProcess {
public void jibBuildFlow() {
// Single command builds and pushes
// mvn compile jib:build
// Jib automatically:
// 1. Analyzes project dependencies
// 2. Creates optimized layer structure
// 3. Builds image without Docker daemon
// 4. Pushes directly to registry
// Benefits:
// - No Dockerfile needed
// - Reproducible builds
// - Optimized layer caching
// - Faster incremental builds
}
public void jibLayerOptimization() {
// Jib creates intelligent layers:
// Layer 1: Dependencies (changes least frequently)
// Layer 2: Resources (changes moderately)
// Layer 3: Classes (changes most frequently)
// This enables optimal caching and faster builds
}
}

Performance Comparison

1. Build Speed Analysis

public class BuildPerformanceComparator {
private static final int ITERATIONS = 10;
public void compareBuildTimes() {
BuildStats dockerStats = runDockerBuilds();
BuildStats jibStats = runJibBuilds();
System.out.println("Docker Average: " + dockerStats.getAverageTime() + "ms");
System.out.println("Jib Average: " + jibStats.getAverageTime() + "ms");
System.out.println("Improvement: " + 
((dockerStats.getAverageTime() - jibStats.getAverageTime()) / 
dockerStats.getAverageTime() * 100) + "%");
}
private BuildStats runDockerBuilds() {
// Simulate Docker build process
long totalTime = 0;
for (int i = 0; i < ITERATIONS; i++) {
long start = System.currentTimeMillis();
// Docker build steps:
// 1. Package application
// 2. Docker context preparation
// 3. Image layer building
// 4. Push to registry
long end = System.currentTimeMillis();
totalTime += (end - start);
}
return new BuildStats(totalTime / ITERATIONS);
}
private BuildStats runJibBuilds() {
// Simulate Jib build process
long totalTime = 0;
for (int i = 0; i < ITERATIONS; i++) {
long start = System.currentTimeMillis();
// Jib build steps:
// 1. Direct dependency analysis
// 2. Optimized layer creation
// 3. Direct registry push
long end = System.currentTimeMillis();
totalTime += (end - start);
}
return new BuildStats(totalTime / ITERATIONS);
}
static class BuildStats {
private final long averageTime;
BuildStats(long averageTime) {
this.averageTime = averageTime;
}
public long getAverageTime() {
return averageTime;
}
}
}

2. Image Size Comparison

public class ImageSizeAnalyzer {
public void analyzeImageSizes() {
ImageMetrics dockerImage = analyzeDockerImage();
ImageMetrics jibImage = analyzeJibImage();
System.out.println("Docker Image Size: " + formatSize(dockerImage.getTotalSize()));
System.out.println("Jib Image Size: " + formatSize(jibImage.getTotalSize()));
System.out.println("Size Reduction: " + 
((dockerImage.getTotalSize() - jibImage.getTotalSize()) / 
(double) dockerImage.getTotalSize() * 100) + "%");
}
private ImageMetrics analyzeDockerImage() {
// Traditional Docker image analysis
return new ImageMetrics(
285 * 1024 * 1024, // 285MB total
150 * 1024 * 1024, // 150MB base image
120 * 1024 * 1024, // 120MB dependencies
15 * 1024 * 1024   // 15MB application
);
}
private ImageMetrics analyzeJibImage() {
// Jib optimized image analysis
return new ImageMetrics(
165 * 1024 * 1024, // 165MB total
120 * 1024 * 1024, // 120MB base image (distroless possible)
40 * 1024 * 1024,  // 40MB optimized dependencies
5 * 1024 * 1024    // 5MB application
);
}
static class ImageMetrics {
private final long totalSize;
private final long baseImageSize;
private final long dependenciesSize;
private final long applicationSize;
// Constructor and getters
}
}

Security Comparison

1. Docker Security Considerations

# Secure Dockerfile example
FROM eclipse-temurin:21-jre-jammy AS base
# Security enhancements
RUN apt-get update && apt-get install -y \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser
# Use minimal base image
FROM base AS runtime
USER appuser
# Copy application
COPY --from=builder --chown=appuser:appuser /app/target/*.jar app.jar
# Security settings
ENV JAVA_TOOL_OPTIONS="\
-Djava.security.egd=file:/dev/./urandom \
-Dfile.encoding=UTF-8"
# Read-only root filesystem
RUN mkdir -p /tmp && chown appuser:appuser /tmp
VOLUME /tmp
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "/app.jar"]

2. Jib Security Features

<!-- Jib with security enhancements -->
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<from>
<image>gcr.io/distroless/java21:nonroot</image>
</from>
<to>
<image>myregistry/myapp:${project.version}</image>
</to>
<container>
<entrypoint>
<arg>java</arg>
<arg>-Djava.security.egd=file:/dev/./urandom</arg>
<arg>-jar</arg>
<arg>/app/myapp.jar</arg>
</entrypoint>
<user>1000</user>
<environment>
<SPRING_PROFILES_ACTIVE>prod</SPRING_PROFILES_ACTIVE>
</environment>
<labels>
<maintainer>[email protected]</maintainer>
<version>${project.version}</version>
</labels>
<format>OCI</format>
<creationTime>USE_CURRENT_TIMESTAMP</creationTime>
</container>
</configuration>
</plugin>

Integration with CI/CD

1. Docker in CI/CD Pipeline

# GitHub Actions with Docker
name: Java CI with Docker
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: 'maven'
- name: Build with Maven
run: mvn -B package -DskipTests
- name: Build Docker image
run: |
docker build -t myapp:${{ github.sha }} .
docker tag myapp:${{ github.sha }} myregistry/myapp:latest
- name: Scan for vulnerabilities
run: |
docker scan myapp:${{ github.sha }}
- name: Push to Registry
run: |
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
docker push myregistry/myapp:${{ github.sha }}
docker push myregistry/myapp:latest

2. Jib in CI/CD Pipeline

# GitHub Actions with Jib
name: Java CI with Jib
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: 'maven'
- name: Build and push with Jib
run: |
mvn compile jib:build \
-Djib.to.image=myregistry/myapp \
-Djib.to.tags=latest,${{ github.sha }} \
-Djib.to.auth.username=${{ secrets.DOCKER_USERNAME }} \
-Djib.to.auth.password=${{ secrets.DOCKER_PASSWORD }}
- name: Run tests
run: mvn test

Advanced Features

1. Jib Extra Directories and Customization

<!-- Advanced Jib configuration -->
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<from>
<image>eclipse-temurin:21-jre-jammy</image>
</from>
<to>
<image>myregistry/myapp:${project.version}</image>
</to>
<container>
<entrypoint>
<arg>java</arg>
<arg>-jar</arg>
<arg>/app/myapp.jar</arg>
</entrypoint>
<environment>
<SPRING_PROFILES_ACTIVE>production</SPRING_PROFILES_ACTIVE>
</environment>
<labels>
<version>${project.version}</version>
<build-date>${maven.build.timestamp}</build-date>
</labels>
<volumes>
<volume>/tmp</volume>
</volumes>
<ports>
<port>8080</port>
</ports>
</container>
<extraDirectories>
<paths>
<path>
<from>src/main/jib</from>
<into>/</into>
</path>
</paths>
</extraDirectories>
<allowInsecureRegistries>false</allowInsecureRegistries>
</configuration>
</plugin>

2. Multi-Platform Builds

public class MultiPlatformBuilds {
// Docker multi-platform build
public void dockerMultiPlatform() {
String dockerfile = """
FROM --platform=$BUILDPLATFORM eclipse-temurin:21-jdk AS builder
WORKDIR /workspace
COPY . .
RUN ./mvnw package -DskipTests
FROM eclipse-temurin:21-jre
COPY --from=builder /workspace/target/*.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
""";
// Build command:
// docker buildx build --platform linux/amd64,linux/arm64 -t myapp:multi .
}
// Jib multi-platform build
public void jibMultiPlatform() {
// Jib supports multi-platform through plugins
// or separate builds for each platform
String mavenCommand = """
mvn compile jib:build \
-Djib.from.platforms=linux/amd64,linux/arm64 \
-Djib.container.creationTime=USE_CURRENT_TIMESTAMP
""";
}
}

Decision Framework

1. When to Choose Docker

public class DockerChoiceCriteria {
public boolean shouldUseDocker(ProjectContext context) {
return context.hasComplexDockerRequirements() ||
context.requiresCustomBaseImages() ||
context.needsMultiStageBuilds() ||
context.hasNonJavaComponents() ||
context.teamFamiliarWithDocker();
}
public static class ProjectContext {
private boolean complexDockerRequirements;
private boolean customBaseImages;
private boolean multiStageBuilds;
private boolean nonJavaComponents;
private boolean dockerFamiliarity;
// Getters and setters
}
}

2. When to Choose Jib

public class JibChoiceCriteria {
public boolean shouldUseJib(ProjectContext context) {
return context.isPureJavaProject() ||
context.requiresFastBuilds() ||
context.hasDockerDaemonIssues() ||
context.needsReproducibleBuilds() ||
context.wantsSimplifiedCICD();
}
public static class ProjectContext {
private boolean pureJavaProject;
private boolean fastBuildsRequired;
private boolean dockerDaemonIssues;
private boolean reproducibleBuilds;
private boolean simplifiedCICD;
// Getters and setters
}
}

Migration Guide

1. Docker to Jib Migration

public class DockerToJibMigrator {
public MigrationResult migrateFromDockerToJib(Project project) {
MigrationResult result = new MigrationResult();
// Step 1: Analyze existing Dockerfile
DockerfileAnalysis analysis = analyzeDockerfile(project.getDockerfile());
// Step 2: Generate equivalent Jib configuration
JibConfiguration jibConfig = generateJibConfig(analysis);
// Step 3: Update build configuration
updateBuildFiles(project, jibConfig);
// Step 4: Test the migration
boolean success = testMigration(project);
result.setSuccess(success);
result.setJibConfiguration(jibConfig);
return result;
}
private DockerfileAnalysis analyzeDockerfile(String dockerfileContent) {
// Parse Dockerfile and extract:
// - Base image
// - Environment variables
// - Exposed ports
// - Entry point
// - Volume mounts
// - User configuration
return new DockerfileAnalysis();
}
}

Conclusion

Summary Comparison

AspectDocker BuildJib
Build SpeedSlower (requires full context)Faster (incremental, daemonless)
Image SizeLarger without optimizationOptimized by default
SecurityManual configuration requiredAutomatic security best practices
ReproducibilityVariableHigh (deterministic builds)
DependenciesRequires Docker daemonNo Docker dependency
ComplexityHigher (Dockerfile knowledge)Lower (Maven/Gradle integration)
CI/CD IntegrationStandard but verboseSimplified and faster

Recommendation

Choose Docker Build when:

  • You need complex multi-stage builds
  • Your application has non-Java components
  • Your team is already proficient with Docker
  • You require custom base images

Choose Jib when:

  • You're building pure Java applications
  • Build speed is critical
  • You want reproducible builds
  • Your CI/CD environment lacks Docker daemon
  • You prefer Maven/Gradle native solutions

For most Java applications, Jib provides significant advantages in build performance, security, and simplicity, while Docker remains the flexible choice for complex containerization scenarios.

Leave a Reply

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


Macro Nepal Helper