DEV Community

Cover image for Java and containers: best practices
Golov Pavel
Golov Pavel

Posted on

Java and containers: best practices

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"]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

Best practice:

COPY sample-runner.jar /deployment/app.jar
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Best practice:

RUN apt-get update \
 && apt-get -y install \
    openjdk-8-jdk ssh vim
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Best practice:

RUN apt-get -y install --no-install-recommends openjdk-8-jdk
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

Best practice:

RUN apt-get update \
 && apt-get -y install --no-install-recommends openjdk-8-jdk
 && rm -rf /var/lib/apt/lists/*
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Best practice:

FROM openjdk
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Best practice:

FROM openjdk:8
Enter fullscreen mode Exit fullscreen mode

Use JRE if you don't need to build your application

Bad practice:

FROM openjdk:8
Enter fullscreen mode Exit fullscreen mode

Best practice:

FROM openjdk:8-jre-alpine
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
pmgysel profile image
Philipp Gysel

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 vs sample-runner.jar)? When the JAR has a version number, it's more unique. But then it's trickier to get the COPY command in the Dockerfile right.

Collapse
 
golovpavel profile image
Golov Pavel • Edited

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.

Collapse
 
alexkay28 profile image
Alex Kay

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.

Collapse
 
golovpavel profile image
Golov Pavel

Thank you for your advice, Alex.
I'll add it to the article soon :)