Reduce Docker Image Size from 1GB to 150MB: Methods to Optimize Docker Images
Content Table
- Why Optimize Docker Images?
- Key Strategies to Optimize Docker Images
- 1. Select a Minimal Base Image
- 2. Use Multistage Builds
- 3. Combine Commands to Minimize Layers
- 4. Clean Up Temporary Files and Caches
- 5. Use `.dockerignore` to Exclude Unnecessary Files
- 6. Analyze Layers with docker history
- Complete Example Dockerfile for Optimizing a JDK-Based Image
- General Docker Image Optimization Tips
- Conclusion
Reducing Docker image size is essential for efficient application deployment and resource management. This guide will focus on optimizing Docker images—particularly for Java applications using JDK images—and provide practical steps to cut down an image from around 1GB to a target of 150MB. Alongside this, we’ll cover general Docker optimization techniques applicable to any image.
Why Optimize Docker Images?
Large Docker images come with various drawbacks, such as:
- Increased Build Times: Large images take longer to build, slowing down development workflows.
- Slower Deployment: Pushing and pulling large images can slow down CI/CD pipelines.
- Higher Storage Costs: Images stored in registries or deployed in production environments consume more storage resources.
For Java applications, Docker images can grow due to the inclusion of the JDK, dependencies, build files, and other artifacts. Optimizing these images helps reduce resource usage and speeds up deployment.
Key Strategies to Optimize Docker Images
These strategies will help reduce the size of any Docker image and are especially relevant for JDK-based images:
1. Select a Minimal Base Image
One of the easiest ways to reduce Docker image size is by starting with a lightweight base image. For Java applications, you can choose:
openjdk:<version>-slim
: A smaller JDK image based on Debian with fewer unnecessary packages.openjdk:<version>-alpine
: An even smaller JDK image based on Alpine Linux, though compatibility can sometimes be an issue with Alpine’smusl
libc library.
Using a slim image instead of the full JDK image can save hundreds of MBs right from the start.
Example: Replace openjdk:17
with openjdk:17-slim
to reduce base image size by approximately 200–300 MB.
2. Use Multistage Builds
With multistage builds, you can separate the build environment from the runtime environment. This approach is especially useful for Java applications, as you can compile your application in a full JDK environment and deploy it in a lighter runtime environment, such as a JRE (Java Runtime Environment).
Here’s a multistage build example with an OpenJDK image to illustrate this optimization.
#Stage 1: Build Stage with Full JDK
FROM openjdk:17 AS builder
WORKDIR /app
#Copy the build files and dependencies
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests
#Stage 2: Runtime Stage with Slim JRE
FROM openjdk:17-slim
WORKDIR /app
#Copy the built application from the previous stage
COPY --from=builder /app/target/myapp.jar /app/myapp.jar
#Set the entry point to run the application
ENTRYPOINT ["java", "-jar", "/app/myapp.jar"]
In this example:
- The Build Stage compiles the application in a full
openjdk:17
image and packages it into a.jar
file. - The Runtime Stage uses
openjdk:17-slim
, a much smaller image that only includes the essentials required to run the.jar
file.
3. Combine Commands to Minimize Layers
Each command in the Dockerfile creates a new layer, which can increase image size. Combining commands where possible helps minimize layers.
Example:
#Before optimization
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean
#Optimized version
RUN apt-get update && apt-get install -y curl && apt-get clean
This combines all the RUN
statements into a single layer, saving space by reducing layer count and clearing any caches immediately after installation.
4. Clean Up Temporary Files and Caches
For JDK images, temporary files from build tools (e.g., Maven or Gradle) are often retained, adding unnecessary weight. Removing these files right after the build process can make a big difference in image size.
For Maven-based applications, clean up the Maven repository cache after the build:
RUN mvn clean package -DskipTests && rm -rf ~/.m2/repository
This ensures that any unused dependencies or temporary build artifacts are removed, keeping only the required .jar
file.
5. Use .dockerignore
to Exclude Unnecessary Files
A .dockerignore
file works similarly to .gitignore
, preventing files and directories that don’t need to be in the image from being copied into the build context. This can help avoid accidentally adding large or sensitive files.
Example .dockerignore
:
.git
target
*.md
.idea
This configuration excludes Git metadata, the target
directory (where build artifacts go), and IDE files from the image, saving space.
6. Analyze Layers with docker history
Use the docker history <image_name>
command to view each layer in your image and identify any unexpectedly large layers. This analysis can help pinpoint which parts of the Dockerfile are adding unnecessary size, guiding further optimizations.
Complete Example Dockerfile for Optimizing a JDK-Based Image
Here’s a comprehensive example Dockerfile that applies all of these tips for a Maven-based Java application. This approach can help bring down a typical Java Docker image size from 1GB to around 150MB.
#Build Stage
FROM openjdk:17 AS builder
WORKDIR /app
#Copy only the necessary files for dependency installation
COPY pom.xml ./
RUN mvn dependency:go-offline
#Copy the application source code and build it
COPY src ./src
RUN mvn clean package -DskipTests && rm -rf ~/.m2/repository
#Runtime Stage
FROM openjdk:17-slim
WORKDIR /app
#Copy only the compiled application from the build stage
COPY --from=builder /app/target/myapp.jar /app/myapp.jar
#Set the entrypoint to run the Java application
ENTRYPOINT ["java", "-jar", "/app/myapp.jar"]
Explanation of the Dockerfile:
- Stage with Full JDK: The initial stage uses
openjdk:17
to compile the application with Maven, storing the compiled output in a.jar
file. - Runtime Stage with Slim JRE: The runtime stage uses
openjdk:17-slim
, a smaller image that only includes what’s necessary to execute the.jar
file. This stage excludes the entire Maven setup and unnecessary files, drastically reducing the image size.
Expected Outcome By using this multistage build, you can cut down the image size significantly. This approach leverages a full JDK only for the build process and deploys with a JRE, allowing you to benefit from both the functionality of a complete JDK and the compactness of a minimal JRE image.
General Docker Image Optimization Tips
While we focused on JDK-specific optimization, the following tips apply to any Docker image:
- Update Base Images Regularly: Updated images often come with optimizations and security patches.
- Keep Images Small in CI/CD: Automated CI/CD workflows benefit from small images, as they are faster to download and deploy.
- Use Container Scanning Tools: Use tools like Docker’s docker scan to check for vulnerabilities and optimize images accordingly.
- Regularly Clean Up Local Images and Containers: Docker caching can leave old images on your local system, which consume disk space. Use docker system prune periodically to remove unused data.
Conclusion
Optimizing Docker images—especially JDK-based images—can significantly improve application deployment efficiency, reduce storage requirements, and accelerate build processes. By using multistage builds, lightweight base images, and Docker-specific techniques like layer and cache management, you can reduce a Java Docker image from over 1GB to around 150MB.
Following these best practices will not only streamline your Docker images but also lead to faster deployment cycles and more efficient resource usage. Start optimizing your Docker images today and experience the benefits of lean, performant containers.