Dockerfile best practices

Dockerfile best practices

Hey Everyone, motivation for this blog was failing to answer this question in an interview

Highlights

  • Using a lightweight base image

  • Analyze the Dockerfile and try to execute a command in a single line which is repeated again and again that is using a multi-line command

  • Use a .dockerignore file to avoid taking into account those files which are not required for building an image

So starting with what is dockerfile and how to make one I would highly suggest you the read the following blog

https://devopscube.com/build-docker-image/

Let's get into the best Dockerfile practices and how to optimize them after learning how to create Docker images from Dockerfile.

Let's get started with a sample DockerFile.

Initial Dockerfile

FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -y openssh-server
RUN apt-get install -y vim
RUN apt-get install -y gcompat;exit 0
WORKDIR /app-files
#Set env variable of ADMIN_USER.
# In future, the value will be retreived from Vault.
ENV ADMIN_USER="appadmin"
#Add Value of ENV Variable to a file.
RUN echo $ADMIN_USER > ./user-creds.txt
#Unset the ENV Variable after value added to file.
RUN unset ADMIN_USER
#Copy The App contents stored in custom-app folder to app-files.
#The txt files in custom-app can be ignored/skipped.
COPY custom-app /app-files 
EXPOSE 8080
# Start app binary  named custom.
CMD ["/app-files/custom"]

Giving some context to the Dockerfile, we have a custom-app folder, which has a binary

We understand how the Dockerfile works; it starts with a base image, in this case, Ubuntu:latest, and then each line of the DockerFile acts as a layer.

  • The apt-get command is used to install the packages and it will automatically update the package list if any new versions are available.

  • The next step is to set up a user account for appadmin with password "appadmin".

  • This can be done by running echo $ADMIN_USER > ./user-creds.txt .

  • After this, unset ADMIN_USER so that no one else can use this account in future steps of the code.

  • Then Copy the custom-app files in the /app-file directory

  • Next, expose it through port 8080

  • Start the application binary executing /app-files/custom

Use this Dockerfile to create an image and record the time it takes using the time -p command.

Building the initial dockerimage

This took over 12 steps.

The size of the Docker image created is 439 MB.

After creating the image, run the following command to run the container using this image:

docker run -d build-app

Analyzing initial Dockerfile

Let's analyse the Dockerfile.

1) The base image used here is Ubuntu; we can change this to Alpine, which will certainly make this lightweight.

Since we are using Alpine, we have to use apk instead of apt-get, and there will be a small change in the package names as well. We can check the exact package names in the Alpine documentation.

https://docs.alpinelinux.org/user-handbook/0.1a/index.html

2) The next step would be instead of using RUN again and again, we can combine all three commands. This would reduce the number of layers that we are adding, which will certainly optimize our Dockerfile.

This is how our updated Dockerfile would look like:

FROM alpine:latest
#RUN apt-get update
#RUN apt-get install -y openssh-server
#RUN apt-get install -y vim
#RUN apt-get install -y gcompat;exit 0

RUN apk add openssh vim gcompat;exit 0
WORKDIR /app-files

#Set env variable of ADMIN_USER.
# In future, the value will be retreived from Vault.
#ENV ADMIN_USER="appadmin"
#Add Value of ENV Variable to a file.
#RUN echo $ADMIN_USER > ./user-creds.txt
#Unset the ENV Variable after value added to file.
#RUN unset ADMIN_USER

RUN export ADMIN_USER="appadmin" \
        && echo $ADMIN_USER > ./user-creds.txt
        && unset ADMIN_USER

#Copy The App contents stored in custom-app folder to app-files.
#The txt files in custom-app can be ignored/skipped.
COPY custom-app /app-files 
EXPOSE 8080
# Start app binary  named custom.
CMD ["/app-files/custom"]

Now have a look at the end of the file; there is a comment that says the.txt files are not part of the build and we can ignore those as blindly adding them to the Docker image would only increase our size and certainly not be an optimization practice. To resolve this, we use the .dockerignore file.

3) Create a .dockerignore file and include the following code in it:

custom-app/*.txt

This will not take into account the text files while building the image.

Now that we have optimized the Dockerfile, let us build the file and have a look at the size and time it takes to build the image.

Comparing initial and updated images

The updated image took 7 steps instead of 12.

Now let's compare the size of the Docker image.

Just 50.1MB, which is significantly less than the original image size of 439MB.

Run the container using the new image.

This confirms that we have successfully deployed applications using the best Dockerfile practices.

Did you find this article valuable?

Support Pranav's Blog by becoming a sponsor. Any amount is appreciated!