Java and containers: best practices
Tags: java, docker
Published: 2020-12-18 08:54:44

# Introduction
This note based on the presentation of [eldermoraes](https://github.com/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.