In this post we will go over some best practices and easy steps to reduce container image size of your spring boot application built using Google Tool Jib.
We will be using the config-server SpringBoot application that was used in this post as a reference to perform image optimization. Please refer to the above post for the setup of this application and ensure you have built its docker image before proceeding with the next steps.
Review Container Image Layers
First lets check how big our image is and various layers within:
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
config-server 1.5 4bdc67d0880c 35 seconds ago 275MB
So thats our starting point, our image for the config-server is 275 MB, lets now inspect various layers within it. Run the following command where 4bdc67d0880c
is the image id.
$ docker image history 4bdc67d0880c
![Container Image layers](https://i0.wp.com/www.dhananjay.blog/wp-content/uploads/2022/09/Screen-Shot-2022-09-14-at-5.20.43-PM.png?resize=640%2C194&ssl=1)
If you notice above the two biggest layers are the dependencies and the host OS or base image (highlighted in red). We will focus on reduce these in the remainder of the post.
Replace Base Container Image
A Base image has no Parent image specified in its Docker file and is created FROM scratch
directive. There are two main projects which we can refer to:
Distroless Container Image
“Distroless” images contain only your application and its runtime dependencies. They do not contain package managers, shells or any other programs you would expect to find in a standard Linux distribution. Refer to the documentation for more details.
These images are generally leaner , but in our case both gcr.io/distroless/java11-debian11 (210MB) and gcr.io/distroless/java17-debian11 (231MB) are already too big to result in any concrete improvements.
Alpine Container Image
These images are based of Alpine Linux distribution which are designed to be small. Instead of using GNU Core utilties, gliblc and systemd alpine uses musl, BusyBox and OpenRC.
If your application can run well with java8, then openjdk:8-jre-alpine is just 84 MB!!. While java 11/17 versions are also worked for alpine, openjdk:8-jre-alpine might work for most workloads pretty well.
Lets replace the base image to this one in our build.gradle file add the following:
jib {
from {
image "openjdk:8-jre-alpine"
}
}
Next, Rebuild the image using gradle jibDocker command, now lets see how much size reduction did we get
$ docker image ls
REPOSITORY TAG IMAGE ID SIZE
config-server 1.5 a4f9feb22a11 139MB
So we reduced the size from 275 MB to 139 MB , thats more than 50% improvement just by replacing the base image.
Review Dependencies in Container Image
Next up, we will review the dependencies. The idea here is to review all dependencies and remove anything that is not used for our use case.
![Container Image Layer](https://i0.wp.com/www.dhananjay.blog/wp-content/uploads/2022/09/Screen-Shot-2022-09-15-at-9.58.13-AM.png?resize=640%2C204&ssl=1)
So we did reduce the base image layer, but we still have the dependency layer ~54 MB, so thats where we will focus next.
Lets print the dependency tree next and see if there is anything that we don’t want , run the following command within the root of the gradle project.
./gradlew -q dependencies --configuration runtimeClasspath
![Dependency Map](https://i0.wp.com/www.dhananjay.blog/wp-content/uploads/2022/09/Screen-Shot-2022-09-15-at-10.03.39-AM.png?resize=640%2C241&ssl=1)
You will get an output similar to the above screenshot. Review the dependencies carefully depending on your use case and start identifying anything that is not needed. For example in this case config-server already comes with logback, so we dont need anything related to log4j in there.
Exclude Unwanted Dependencies
Once the dependencies are identified the next step is to exclude them from the image, this can be done simply by adding the following to your gradle file, this removes this dependencies for all configurations.
configurations {
all {
exclude group: "org.apache.logging.log4j", module: "log4j-to-slf4j"
exclude group: "org.apache.logging.log4j", module: "log4j-api"
}
}
After image rebuild you will notice a slight difference in size , since log4j jar files are less than a MB. Using this approach your mileage might vary depending on how optimized your dependencies already are.
There are multiple ways to exclude dependencies which gives you more control over what is being removed. Refer to the Gradle documentation for more details.
More Considerations
- Reduce no of Layers within the image.
- Don’t embed application data or configuration within the image.
- Reduce Dependencies by using components from the smaller no of libraries to reduce size.