Optimizing Docker Images for Java Applications on Azure Container Apps
Introduction
In the cloud-native era, the need for rapid application startup and automated scaling has become more critical, especially for Java applications, which require enhanced solutions to meet these demands effectively. In a previous blog post Accelerating Java Applications on Azure Kubernetes Service with CRaC, we explored using CRaC technology to address these challenges. CRaC enables faster application startup and reduces recovery times, thus facilitating efficient scaling operations. In this blog post, we’ll delve further into optimizing container images specifically for Azure Container Apps (ACA), by leveraging multi-stage builds, Spring Boot Layer Tools, and Class Data Sharing (CDS) to create highly optimized Docker images. By combining these techniques, you’ll see improvements in both the image footprint and the startup performance of your Java applications on ACA. These improvements make Java applications more agile and responsive to frequent cloud-native deployments, ensuring they can keep pace with modern operational demands
Key Takeaways
- Multi-stage builds reduced the image size by 33%, leading to faster image pulls.
- Spring Boot Layer Tools further optimized the build process by reducing unnecessary rebuilds, slightly improving both image pull and startup times.
- Class Data Sharing (CDS) provided the most impactful benefit, reducing application startup time by 27%, significantly enhancing runtime performance.
Overview of Optimization Techniques
Multi-Stage Builds
Spring Boot Layer Tools
Class Data Sharing (CDS)
Applying the Techniques: Step-by-Step Optimization
Step 0: Starting Point: The Base Docker Image
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu
WORKDIR /home/app
ADD . /home/app/spring-petclinic-main
RUN cd spring-petclinic-main && ./mvnw -Dmaven.test.skip=true clean package && cp ./target/*.jar /home/app/petclinic.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "petclinic.jar"]
Base Image Metrics for Comparison
Optimization Stage | Image Size (MB) | Image pull time (s) | Startup Time (s) |
Base Image (No Optimization) | 734 | 8.017 | 7.649 |
Step 1: Using Multi-Stage Builds
# Stage 1: Build Stage
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS builder
WORKDIR /home/app
ADD . /home/app/spring-petclinic-main
RUN cd spring-petclinic-main && ./mvnw -Dmaven.test.skip=true clean package
# Stage 2: Final Stage
FROM mcr.microsoft.com/openjdk/jdk:17-mariner
WORKDIR /home/app
EXPOSE 8080
COPY --from=builder /home/app/spring-petclinic-main/target/*.jar petclinic.jar
ENTRYPOINT ["java", "-jar", "petclinic.jar"]
Multi-Stage Build Metrics
Optimization Stage | Image Size (MB) | Image pull time (s) | Startup Time (s) |
Base Image (No Optimization) | 734 | 8.017 | 7.649 |
Multi-Stage Build | 492 | 7.145 | 7.932 |
Step 2: Optimizing with Spring Boot Layer Tools
# Stage 1: Build Stage
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS builder
WORKDIR /home/app
ADD . /home/app/spring-petclinic-main
RUN cd spring-petclinic-main && ./mvnw -Dmaven.test.skip=true clean package
# Stage 2: Layer Tool Stage
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS optimizer
WORKDIR /home/app
COPY --from=builder /home/app/spring-petclinic-main/target/*.jar petclinic.jar
RUN java -Djarmode=layertools -jar petclinic.jar extract
# Stage 3: Final Stage
FROM mcr.microsoft.com/openjdk/jdk:17-mariner
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
COPY --from=optimizer /home/app/dependencies/ ./
COPY --from=optimizer /home/app/spring-boot-loader/ ./
COPY --from=optimizer /home/app/snapshot-dependencies/ ./
COPY --from=optimizer /home/app/application/ ./
Layer Tools Metrics
Optimization Stage | Image Size (MB) | Image pull time (s) | Startup Time (s) |
Base Image (No Optimization) | 734 | 8.017 | 7.649 |
Multi-Stage Build | 492 | 6.987 | 7.932 |
Spring Boot Layer Tools | 493 | 7.104 | 7.805 |
Step 3: Integrating Class Data Sharing (CDS)
# Stage 1: Build Stage
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS builder
WORKDIR /home/app
ADD . /home/app/spring-petclinic-main
RUN cd spring-petclinic-main && ./mvnw -Dmaven.test.skip=true clean package
# Stage 2: Layer Tool Stage
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS optimizer
WORKDIR /app
COPY --from=builder /home/app/spring-petclinic-main/target/*.jar petclinic.jar
RUN java -Djarmode=tools -jar petclinic.jar extract --layers --launcher
# Stage 3: Optimize with CDS Stage
FROM mcr.microsoft.com/openjdk/jdk:17-mariner
COPY --from=optimizer /app/petclinic/dependencies/ ./
COPY --from=optimizer /app/petclinic/spring-boot-loader/ ./
COPY --from=optimizer /app/petclinic/snapshot-dependencies/ ./
COPY --from=optimizer /app/petclinic/application/ ./
RUN java -XX:ArchiveClassesAtExit=./application.jsa -Dspring.context.exit=onRefresh org.springframework.boot.loader.launch.JarLauncher
ENTRYPOINT ["java", "-XX:SharedArchiveFile=application.jsa", "org.springframework.boot.loader.launch.JarLauncher"]
Optimizing with Class Data Sharing (CDS)
Optimization Stage | Image Size (MB) | Image pull time (s) | Startup Time (s) |
Base Image (No Optimization) | 734 | 8.017 | 7.649 |
Multi-Stage Build | 492 | 6.987 | 7.932 |
Spring Boot Layer Tools | 493 | 7.104 | 7.805 |
CDS | 560 | 7.145 | 5.562 |