Creating Lightweight Java Container with Custom JRE
Table of Contents
Java applications, often packaged in large container images, can benefit significantly from optimization techniques that reduce their footprint. This is crucial for efficient resource utilization, especially in cloud environments. In this post, we explore how to use jdeps
and jlink
, tools included with Amazon Corretto’s OpenJDK, to analyze dependencies and create a minimal custom Java Runtime Environment (JRE) that can substantially reduce the size of Docker images for Java applications.
Initial Docker Setup #
A typical Docker setup for a Spring Boot application might start with a basic Dockerfile like this:
FROM amazoncorretto:11.0.20-alpine
COPY ./sample-app.jar /app/sample-app.jar
WORKDIR /app
EXPOSE 8000
ENTRYPOINT ["/jre/bin/java", "-jar", "/app/app.jar"]
This Dockerfile sets up a Java environment using Amazon Corretto’s Alpine-based image. It copies the application JAR into the image and sets the working directory. The application is exposed on port 8000.
Utilizing jdeps and jlink #
jdeps
is a Java Dependency Analysis Tool that helps determine the specific modules required by a Java application. jlink
is a Java Linker that can create a custom JRE based on the modules needed. The combined use of these tools can significantly reduce the Java runtime’s size.
Here’s a basic example of how jdeps
can be used:
jdeps --ignore-missing-deps --print-module-deps -q --recursive --multi-release 17 --class-path="./*" --module-path="./*" ./sample-app.jar
The output might look something like this:
java.base,java.management
With these modules identified, jlink
can create a stripped-down version of the JRE:
jlink \
--verbose \
--add-modules java.base,java.management \
--strip-debug \
--no-man-pages \
--no-header-files \
--compress=2 \
--output customjre
Building the Optimized Docker Image #
Using the above insights, we can construct a multi-stage Dockerfile that builds a minimal custom JRE and then creates a lightweight container for the application:
# STAGE 1: Dependency analysis
FROM amazoncorretto:17.0.2 AS deps
WORKDIR /root
COPY ./sample-app.jar /root/sample-app.jar
RUN $JAVA_HOME/bin/jdeps \
--ignore-missing-deps \
--print-module-deps \
-q --recursive \
--multi-release 17 \
/root/sample-app.jar > /deps.info
# STAGE 2: JRE creation
FROM amazoncorretto:17.0.2 AS corretto-jdk
RUN yum install -y binutils # needed for jlink
COPY --from=deps /deps.info /deps.info
RUN $JAVA_HOME/bin/jlink \
--verbose \
--add-modules $(cat /deps.info) \
--strip-debug \
--no-man-pages --no-header-files \
--compress=2 \
--output /root/customjre
# STAGE 3: Final image
FROM debian:bookworm-slim
ENV JAVA_HOME=/jre
ENV PATH="${JAVA_HOME}/bin:${PATH}"
COPY --from=corretto-jdk /root/customjre/ /jre/
COPY ./sample-app.jar /app/
WORKDIR /app
EXPOSE 8000
CMD ["java", "-jar", "sample-app.jar"]
Results and Benefits #
By using a custom JRE, we can reduce the container size dramatically. In one of our tests, the image size was reduced by over 60%, dropping from 563MB to just 225MB.
container w/ corretto:17.0.2 | container w/ customjre | rate | |
---|---|---|---|
Size | 563MB | 225MB | decreased 60.06% |
Troubleshooting #
These are some issues that I had while going thorugh above steps.
Issue 1: “*/jre/bin/java no such file or directory” with Alpine Images
- Alpine images use musl libc instead of glibc, which can cause issues with binaries expecting glibc. Switching to a Debian-based image or installing glibc in Alpine can resolve these problems.
Issue 2: Docker Image Size Discrepancies
- Docker images may appear smaller on registries like Docker Hub because layers are compressed. Actual sizes may vary when pulled locally.
Conclusion #
Optimizing Java container images with jdeps
and jlink
is an effective way to reduce their size and improve deployment efficiency. This approach is particularly beneficial for environments where resource constraints are significant, such as Kubernetes clusters running multiple microservices.