Writing Efficient Dockerfiles
Minimize Layers
It is important to understand the concept of layers in a Docker container image.
Try and keep the number of layers to a minimum to reduce the final image size and also for better performance.
Info
Only RUN, COPY, and ADD Docker instructions create layers.
Chain multiple RUN commands into a single command with the help of “&&” and line separators (“\”). For example, the following snippet creates three separate layers, one for each RUN instruction:
RUN wget -O myfile.tar.gz http://example.com/myfile.tar.gz
RUN tar -xvf myfile.tar.gz -C /usr/src/myapp
RUN rm myfile.tar.gz
RUN wget -O myfile.tar.gz http://example.com/myfile.tar.gz && \
tar -xvf myfile.tar.gz -C /usr/src/myapp && \
rm myfile.tar.gz
user@ubuntu1804vm:~$ docker image history debian:buster-slim
IMAGE CREATED CREATED BY SIZE COMMENT
c7346dd7f20e 3 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:3af3091e7d2bb40bc… 69.2MB
Use .dockerignore
This works just like .gitignore. You can specify files or folders in the repository that are not relevant to the container build by adding them to this file.
Package Installation
Only install those packages that are absolutely necessary for the application to run. A separate debug variant of the container image can be generated which could include “nice-to-have” packages such as a shell, process viewer, tools, etc.
If one or more packages are installed, remove the package manager cache in the same RUN instruction. For example :
RUN apt-get update && \
apt-get -y install --no-install-recommends openjdk-8-jdk && \
rm -rf /var/lib/apt/lists/*
Ordering of Instructions
In general it is a good practice to order instructions from least to most frequently changing steps in the Dockerfile. This is to optimize the build cache feature of Docker.
The following example illustrates a non-optimal ordering of instructions. The RUN command is not expected to change often but if the files being copied in the COPY instruction are modified, then all cached layers downstream from that step are invalidated. This means those layers will be generated (re-built) again, which will slow down build times.
FROM debian
COPY . /app
RUN apt-get update && \
apt-get -y install openjdk-8-jdk ssh vim
CMD ["java", "-jar", "/app/app.jar"]
This can be easily fixed by moving down the COPY command. This will ensure that the cached RUN layer will be used during the build, thereby speeding up the build process.
FROM debian
RUN apt-get update && \
apt-get -y install openjdk-8-jdk ssh vim
COPY . /app
CMD ["java", "-jar", "/app/app.jar"]
Avoid latest Tags
It is best to avoid using base images tagged as “latest” in production build environments. This is because “latest” always points to the most recent base image build, so next time a container build is run, the base image used in the build may not be the same as in the previous builds, which will cause potential issues. A better practice is to use a specific tag. Sensia has recommended base images, see section Base Images.
So, instead of :
FROM gcc:latest
use :
FROM gcc:7
Further reading
Info
Info
References
- https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
- https://www.docker.com/blog/intro-guide-to-dockerfile-best-practices/
- https://docs.docker.com/storage/storagedriver/#images-and-layers
- https://www.docker.com/blog/speed-up-your-development-flow-with-these-dockerfile-best-practices/
- https://learnk8s.io/blog/smaller-docker-images
- https://www.baeldung.com/linux/docker-build-cache