Introduction
This note based on the presentation of eldermoraes.
https://www.youtube.com/watch?v=R4kxLsXkAE4.
Java and Containers
Common issues
- Long build time
- Huge image size
- Hard maintainability
- Resource allocation
Initial docker file
Let's look at the initial docker file:
FROM debian:stretch
COPY lib/* /deployment/lib/
COPY *-runner.jar /deployment/
RUN apt-get update
RUN apt-get -y install openjdk-8-jdk ssh vim
CMD ["java", "-jar", "/deployment/sample-runner.jar"]
This file has a bunch of problems, listed before. We will improve this file during the article.
How to avoid long build time
Put static layers at the top and changing layers at the bottom of the docker file
If we put changing layers at the top of the docker file and change these layers, it triggers to rebuild all the bottom layers that not been changed.
Bad practice:
COPY lib/* /deployment/lib/
COPY sample-runner.jar /deployment/
RUN apt-get update
RUN apt-get -y install openjdk-8-jdk ssh vim
Best practice:
RUN apt-get update
RUN apt-get -y install openjdk-8-jdk ssh vim
COPY lib/* /deployment/lib/
COPY sample-runner.jar /deployment/
Order of commands in the docker file is the matters!
Copy files with specific names
Try to don't use the wildcard copy command because if you create a file in the directory with the target file, the cache will be broken.
Bad practice:
COPY *-runner.jar /deployment/
Best practice:
COPY sample-runner.jar /deployment/app.jar
Group layers
Instead of having a list of commands, try to group your commands. It reduces cache size, images size and layers count.
Bad practice:
RUN apt-get update
RUN apt-get -y install openjdk-8-jdk ssh vim
Best practice:
RUN apt-get update \
&& apt-get -y install \
openjdk-8-jdk ssh vim
How to get rid of huge image size
Use flag --no-install-recommends
for apt-get
command
Don't install not necessary staff into your container.
Bad practice:
RUN apt-get -y install openjdk-8-jdk
Best practice:
RUN apt-get -y install --no-install-recommends openjdk-8-jdk
Remove cache of your apt manager
For apt-get command use rm -rf /var/lib/apt/lists/*
command.
Bad practice:
RUN apt-get update \
&& apt-get -y install --no-install-recommends openjdk-8-jdk
Best practice:
RUN apt-get update \
&& apt-get -y install --no-install-recommends openjdk-8-jdk
&& rm -rf /var/lib/apt/lists/*
How to stay away from hard maintainability
Use specific images
No need to use the Debian base image if you deploy your java application. Use the official language/framework image (like openjdk-alpine).
Bad practice:
FROM debian:stretch
Best practice:
FROM openjdk
Always specify base image tag
It didn't break your build and deploy in the case when the base image will be updated.
Bad practice:
FROM openjdk
Best practice:
FROM openjdk:8
Use JRE if you don't need to build your application
Bad practice:
FROM openjdk:8
Best practice:
FROM openjdk:8-jre-alpine
Java resource allocation
Before Java 8u121 Java didn't know about process cgroups, therefore Java application didn't know about resource limitations.
It's not recommended to use Java version before 8u121 in the container environment.
Result
Let's look at the result docker file.
FROM openjdk:8-jre-alpine
COPY lib/* /deployment/lib/
COPY sample-runner.jar /deployment/app.jar
CMD ["java", "-jar", "/deployment/app.jar"]
Using this file docker break the cache only if sample-runner.jar
file will be changed. Moreover, it breaks only the last layer cache. Therefore, the build time was optimized.
The size of the initial docker image is 662MB and the size of the result docker image is 95.3MB. It's perfect optimization!
The result docker file looks compact and high-maintainable.
Top comments (4)
Great post thanks 😀 You bring up important optimizations here.
Quick question: Let's assume you want to automatically build your web service and then build a Docker image. What do you recommend, artifacts with or without version number (e.g.
sample-runner-0.0.1.jar
vssample-runner.jar
)? When the JAR has a version number, it's more unique. But then it's trickier to get theCOPY
command in theDockerfile
right.Hi, Philipp, thank you for your comment!
I think that it's not very important for the application to have a certain version in the jar file name because you pack it into a container and don't share the jar file separately.
In case then you build a library, it's important to have a unique jar version. But in this case, you don't pack it into the container and simply deploy the jar file into artifactory.
Pavel, thank u!
Could u add the complete dockers at the end of your post? It would be really comfortable to compare them in general.
Thank you for your advice, Alex.
I'll add it to the article soon :)