Dockerfile and its Best Practices

docker
Reading Time: 4 minutes

If you are working with docker, you would inevitably have to write a dockerfile. It’s a text file containing a set of commands which are executed in a sequential order in which they are written to create a new docker image.

It is essential that you follow best practices when writing a dockerfile, so that your build is more efficient and reliable. In this blog, we will learn about some best practices to follow while creating a dockerfile.

– Using Build Context ( . )

Build context includes files and directories that are accessible to commands in a Dockerfile. Using a larger build context that includes unnecessary files should be avoided as it will lead to longer builds.

docker build .

This command will refer to all the files in the current working directory to build the image using Dockerfile.

– Minimizing Number of Layers created by Dockerfile

RUN, COPY and ADD are the three Docker instructions that create layers. Reduced use of these instructions will result in a minimized number of layers.

RUN apt-get update
RUN apt-get install -y <package>

Use of combined multiple RUN instructions as follows:

RUN apt-get update && apt-get install -y <package>

– Using Multi-Stage Builds in a Dockerfile

Multi-Stage Build allows using multiple images in a Dockerfile. Multiple stages allow to use artifacts from one stage in the another one which results in decreased size of the final image.

# base build image
FROM maven:3.6.0-jdk-8 as maven

# copy the project file
COPY ./pom.xml ./pom.xml

# copy  other files
COPY ./src ./src

# build for release
RUN mvn package 

# our final base image
FROM openjdk:8-jre-alpine

# set deployment directory
WORKDIR /my-project

# copy the built artifact from the maven image
COPY --from=maven target/springboot-starterkit-1.0.jar ./

# set the startup command to run the binary
CMD ["java", "-jar", "./springboot-starterkit-1.0.jar"]

For instance, the above Dockerfile includes two separate stages. The first stage that is Stage 0 builds the application while second stage that is Stage 1 uses the artifact from previous stage to run the application.

– Using .dockerignore

Using the .dockerignore file allows to exclude files from the build context bringing more efficiency to the Docker builds. A list of files and directories can be specified in the .dockerignore file. As a result, making the build fast and light by excluding some of the files that are not relevant to the build.

– Choosing the Right Base Image for Dockerfile

In addition to other best practices, choosing an appropriate base image in the Dockerfile is equally important. for an effective build. The base image should be chose based on the following points:

  • Use official docker images
  • Using base images that are smaller in size
  • Using images with their specific tags
# pulling official small-sized base image with its specific version 
FROM node:13.12.0-alpine

– Leverage build cache

Leveraging your cache involves layering your images so that only the bottom layers change often. The RUN instructions that change more frequently towards the bottom of the Dockerfile, while steps that change less often should be ordered towards the top.

When building an image, Docker follows the instructions in Dockerfile. At each instruction, Docker searches for an existing image in its cache instead of creating a new similar image.

Starting with a parent image that is already in the cache, the next instruction is compared against all child images derived from that base image to see if one of them was built using the exact same instruction. If not, the cache is invalidated.

– Building images using BuildKit

BuildKit is a feature of Docker that is available in the latest releases of Docker. BuildKit enables high-performance Docker builds and the possibility of caching to decrease build times. It also includes support for passing secret information for building a new image.

BuildKit can be enabled in two ways:

  • When running a build
  • Edit the daemon configuration file /etc/docker/daemon.json set the feature to true
DOCKER_BUILDKIT=1 docker build .
{ "features": { "buildkit": true } }

– Security

Some best practices to avoid common docker security issues include:

  • Using a non-root user to run container-
    Changing the privilege of a container will prevent the Docker host’s malicious root access, that is, everyone who pulls the docker image will not be able to get access of the application environment adding an extra layer of security. Changing to a non-root privilege can be done by using USER in dockerfile to set the user.
    # Adding a new user "user1" 
    RUN useradd -u user1
    # Changing to non-root privilege
    USER user1
  • Scanning vulnerabilities of docker images-
    This allows to review the security state of images and also take actions on the identified issues.
    The docker scan command in docker will scan the image and will show the line of dockerfile that introduces a vulnerability.
    The easiest way to scan an image is using docker scan with the image name.
    docker scan hello-world 
  • Avoid using sensitive information in images-
    The exposing of sensitive information in the layers of dockerfile of the image should be prevented.
    If the image is to be shared publicly, avoid the use of private information along with the COPY and ADD instructions.
    Instead the sensitive files can be added at run time with the docker urn command.

Conclusion

In this post, I’ve covered techniques for optimizing the build of images. These build recommendations will provide effectual docker image builds.

Leave a Reply