Introduction:
In the fast-paced world of application deployment, optimizing Docker images is essential for enhancing efficiency and scalability. Large Docker images can slow build times, increase storage costs, and complicate deployments.
This blog explores practical techniques for optimizing Docker images, focusing on multi-stage builds and JLink. I will use the Spring PetClinic application as a case study to demonstrate how to reduce image sizes while ensuring they remain production-ready significantly.
Whether your goal is to accelerate CI/CD pipelines or create more secure deployments, this guide will help you achieve lightweight, efficient containers tailored to your application’s needs.
Method 1: The Original Dockerfile (Before Optimization)
Our initial Dockerfile is quite simple. It utilizes Maven to build the application and includes all the necessary dependencies for compilation and runtime. Although this approach works, it results in a large image because it incorporates unnecessary build tools and dependencies in the final runtime environment.
Here is an example of what the initial Dockerfile might look like:
# Initial Dockerfile: Build and Run in One Step (Non-Optimized)
FROM maven:3.9.4-eclipse-temurin-17-alpine
WORKDIR /app
# Copy source code and build the application
COPY . .
RUN mvn clean package -DskipTests
# Run the application
ENTRYPOINT ["java", "-jar", "target/spring-petclinic-*.jar"]
This method generates a large image (approximately 400–500MB) since it includes Maven and all build dependencies that are not required for running the application in production.
Method 2: Optimizing the Dockerfile Using Multi-Stage Builds
- Using a Multi-Stage Build: We divided the build and runtime stages to retain only what is necessary for the final image.
- Caching Dependencies: We first copied the
pom.xml
file to cache dependencies, which speeds up future builds. - Isolating Build Artifacts: We included only the compiled JAR file in the runtime image, omitting unnecessary build files.
These steps eliminated Maven and build-related files from the final image, resulting in a more efficient and smaller Docker image.
# Use a Maven image with JDK 17 for the build stage
FROM maven:3.9.4-eclipse-temurin-17-alpine AS build
WORKDIR /app
# Copy the pom.xml first and download the dependencies
COPY pom.xml ./
RUN mvn dependency:go-offline -B
# Copy the source code after dependencies are cached
COPY src ./src
# Package the application
RUN mvn clean package -DskipTests -Ddockerfile.skip=true
# Use JDK 17 for the runtime stage
FROM eclipse-temurin:17-jdk-alpine AS runtime
WORKDIR /app
# Copy the built JAR from the build stage
COPY --from=build /app/target/spring-petclinic-*.jar /app/app.jar
# Define the entry point
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
Implementing these changes successfully reduced the image size from 518 MB to 396 MB. The final runtime image now includes only the essential Java Development Kit (JDK) and the compiled application JAR, eliminating the unnecessary overhead from Maven and other build dependencies.
Method 3: Optimizing Your Dockerfile for Advanced Performance
Multi-Stage Builds for Creating a Lightweight Production Image with a Minimal Java Runtime
Here’s a clearer version of the text:
“Let’s examine the optimized Dockerfile that utilizes multi-stage builds.”
# Stage 1: Build Stage
FROM maven:3.8.5-openjdk-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests
# Stage 2: Create minimal Java runtime with JLink
FROM eclipse-temurin:17-jdk-alpine AS jlink
RUN $JAVA_HOME/bin/jlink \
--module-path $JAVA_HOME/jmods \
--add-modules java.base,java.logging,java.xml,java.naming,java.sql,java.management,java.instrument,jdk.unsupported,java.desktop,java.security.jgss \
--output /javaruntime \
--compress=2 --no-header-files --no-man-pages
# Stage 3: Final Stage
FROM alpine:3.17
WORKDIR /app
COPY --from=jlink /javaruntime /opt/java-minimal
ENV PATH="/opt/java-minimal/bin:$PATH"
COPY --from=build /app/target/*.jar /app/app.jar
EXPOSE 8081
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
Breaking Down the Steps for Optimization:
This Dockerfile efficiently uses a multi-stage build for a Spring Boot application, minimizing image size.
1. Build Stage:
- Base: maven:3.8.5-openjdk-17
- Purpose: To download dependencies and compile the application while caching dependencies to speed up future builds.
2. JLink Runtime Optimization:
- Base: eclipse-temurin:17-jdk-alpine
- Purpose: To create a minimal Java runtime using jlink that includes only the necessary modules while compressing and removing unneeded files to keep it small.
3. Final Stage:
- Base: alpine:3.17
- Purpose: To combine the minimal Java runtime with the application JAR, creating a lightweight and production-ready image.
Key Advantages of This Optimization
- Smaller Image Size: The size was reduced from approximately 518 MB to just 128 MB.
- Faster Deployment and Scaling: Smaller images load faster, accelerating deployment times, particularly in cloud environments.
- Reduced Attack Surface: By removing unnecessary tools and libraries, the optimized image becomes more secure and less vulnerable to attacks.
The Results: Comparison Before and After Optimization
- Initial Image Size: ~The Dockerfile, when using Maven without optimization, has a size of 518 MB.
- Optimized Image Size: ~The size is 128 MB when using the multi-stage Dockerfile with JLink.
To determine the image associated with the running petclinic-test container, use the following docker inspect command:
docker inspect --format='{{.Config.Image}}' petclinic-test
Alternatively, you can use the command docker ps
along with the — filter
option to specifically target your container:
docker ps --filter "name=petclinic-test" --format "{{.Image}}"
Access the PetClinic application using your web browser:
Conclusion:
Optimizing Docker images goes beyond simply reducing their size; it involves creating production-ready containers that are fast, secure, and cost-effective. Techniques such as using multi-stage builds and creating minimal runtimes with JLink can significantly decrease image sizes, improve build performance, and reduce vulnerabilities.
For example, with the Spring PetClinic application, we achieved a size reduction from 518MB to 128MB, demonstrating the clear benefits of these strategies. Adopting these best practices can accelerate your deployments, save resources, and enhance security in your production environments. Start optimizing today to unlock Docker's full potential!
What are your thoughts on this article? Feel free to share your opinions in the comments below — or above, depending on your device! If you enjoyed the story, please consider supporting me by clapping, leaving a comment, and highlighting your favorite parts.
Visit subbutechops.com to explore the fascinating world of technology and data. Get ready for more exciting content. Thank you, and happy learning!
Top comments (0)