Introduction

Docker has become an integral part of modern software development, allowing developers to package their applications and dependencies into containers for consistent and efficient deployment. When it comes to Java applications, optimizing the Docker image size is crucial for faster deployment, efficient resource utilization, and overall better performance. In this article, we’ll explore various strategies to reduce the size of Java Virtual Machine (JVM) Docker images, accompanied by practical code examples.

Use a Minimal Base Image

One of the most effective ways to reduce the size of a JVM Docker image is to start with a minimal base image. Instead of using a generic distribution, consider using Alpine Linux or a similar lightweight base image. This approach significantly reduces the image size by excluding unnecessary components that might come with a full-fledged operating system.

Dockerfile
# Use a minimal base image
FROM adoptopenjdk:11-jre-hotspot-alpine
# Copy application JAR file
COPY target/my-app.jar /app/# Set the working directory
WORKDIR /app# Specify the command to run on container start
CMD [“java”, “-jar”, “my-app.jar”]

In this example, we’re using the adoptopenjdk:11-jre-hotspot-alpine image as the base image, which includes only the essential components needed to run Java applications.

Multi-Stage Builds

Multi-stage builds allow you to use multiple FROM statements in your Dockerfile, creating intermediate images for different stages of the build process. This can significantly reduce the final image size by discarding unnecessary build artifacts and dependencies after they have been used.

Dockerfile
# Stage 1: Build the application
FROM maven:3.8.4-openjdk-11-slim AS builder
COPY . /app
WORKDIR /app
RUN mvn clean package# Stage 2: Create a minimal runtime image
FROM adoptopenjdk:11-jre-hotspot-alpineCOPY –from=builder /app/target/my-app.jar /app/WORKDIR /app

CMD [“java”, “-jar”, “my-app.jar”]

In this example, the first stage uses a Maven image to build the application and create the JAR file. The second stage starts with a minimal runtime image, copying only the necessary artifacts from the first stage.

Optimize Dependencies

Carefully manage your application dependencies to include only the libraries and components required for runtime. Remove unnecessary dependencies, and consider using a tool like jlink to create a custom runtime image containing only the modules needed by your application.

Dockerfile
# Use a minimal base image
FROM adoptopenjdk:11-jre-hotspot-alpine
# Copy custom runtime image
COPY target/image /app/WORKDIR /appCMD [“bin/my-app”]

Here, the application’s custom runtime image, generated using jlink, is copied to the Docker image. This ensures that only the necessary parts of the Java runtime are included.

Optimize Docker Layers

Docker layers play a crucial role in image caching and efficiency. Be mindful of the order of commands in your Dockerfile to maximize layer reuse. Group related commands together and leverage the cache whenever possible.

Dockerfile
# Use a minimal base image
FROM adoptopenjdk:11-jre-hotspot-alpine
# Copy only the necessary files for dependency resolution
COPY pom.xml /app/WORKDIR /app# Download and resolve dependencies first
RUN mvn dependency:go-offline# Copy the rest of the application files
COPY src /app/src

# Build the application
RUN mvn package

CMD [“java”, “-jar”, “target/my-app.jar”]

In this example, by copying only the pom.xml file initially, we ensure that Maven dependencies are resolved separately from the application source code. This allows Docker to cache the dependency resolution step if the pom.xml file hasn’t changed.

Clean Up Unnecessary Files

Remove unnecessary files and directories from your final Docker image to further reduce its size. This can include build artifacts, temporary files, and any files not required for runtime.

Dockerfile
# Use a minimal base image
FROM adoptopenjdk:11-jre-hotspot-alpine
# Copy application JAR file
COPY target/my-app.jar /app/# Set the working directory
WORKDIR /app# Remove unnecessary files
RUN rm -rf /app/docs /app/test# Specify the command to run on container start
CMD [“java”, “-jar”, “my-app.jar”]

In this example, we remove the docs and test directories after copying the application JAR file. Adjust this step according to your application’s specific requirements.

Conclusion

Optimizing JVM Docker image size is crucial for efficient resource usage and faster deployment. By following the strategies outlined in this article and using the accompanying code examples, you can significantly reduce the size of your Java applications’ Docker images. Choose a minimal base image, implement multi-stage builds, optimize dependencies, structure your Dockerfile for layer efficiency, and clean up unnecessary files to create lean and efficient Docker images for your Java applications.