Writing a Dockerfile. Best practics

We publish a new translation and hope that the author's recommendations will help you optimize your Docker image.


Since its inception, Docker has revolutionized the way we use containers. This is mainly due to the simplicity that Docker provides. It can be used without even understanding any complex container topics.







If you are new to Docker, you can choose a template (base image) and define your instructions (Dockerfile commands) to put your code inside the image and run it.



The simplicity of Docker will help you with your work from the very beginning of its use, and the skill to optimize it comes with experience and usually takes time.



I have been working with Docker for a long time, so I decided to share my experience on how to create containers better, even if you are a beginner.



1. Define cached units ➱



Did you know that every RUN command included in the Dockerfile affects the caching level?



Using multiple RUN commands to install packages will affect the performance and efficiency of the build process. Using one RUN command to install all packages and dependencies will help you create one cached unit instead of several.



RUN apt-get update && apt-get install -y \
    aufs-tools \
    automake \
    build-essential \
    curl \
    dpkg-sig \
    libcap-dev \
    libsqlite3-dev \
    mercurial \
    reprepro \
    ruby1.9.1 \
    ruby1.9.1-dev \
    s3cmd=1.1.*

      
      





2. Reduce the image size



Image size plays an important role in making a good Dockerfile. Using smaller images facilitates faster deployment and reduces the potential for attacks.



Remove unnecessary dependencies



Avoid installing unnecessary tools, such as debugging tools, into the image.



If the package manager automatically uses the recommended packages to install, use the package manager flags and avoid installing unnecessary dependencies.



RUN apt-get update && apt-get -y install --no-install-recommends
      
      





Tip: Share reusable components between projects using Bit ( Github ).



Bit makes it easy to document, share, and reuse independent components between projects. Use it to reuse code, maintain design consistency, team collaboration, increase delivery speed, and build scalable applications.



Bit supports Node, TypeScript, React, Vue, Angular and more.





Explore the components published on Bit.dev



3. Image support



Choosing the right base image for your application is very important.



Use the official Docker image



Using the official Docker image reduces its size by reducing unnecessary dependencies that make the image larger. There are 3 main benefits to using the official image:



  • allows us to use an image based on best practices,
  • ensures the reliability of the image and its safety,
  • enhances trust and safety.


#    
FROM node:13.12.0-alpine

#   
WORKDIR /app

#  `/app/node_modules/.bin`  $PATH
ENV PATH /app/node_modules/.bin:$PATH
      
      





Use specific tags



When choosing a base image, it is recommended to use a specific tag. Do not use the latest tag for the image . The Latest tag can undergo breaking changes over time.



#    
FROM node:13.12.0-alpine
      
      





Use a minimal build version A



minimal build reduces the size of the final image. This helps you deploy applications faster and more securely.







As you can see from the above example, when using the minimal build, the image size is smaller. Most images use the alpine assembly. Alpine is a very lightweight image with a standard 2MB size.



By using an alpine based image, we can significantly reduce the size of the resulting image.



4. Reproducibility



Building from source in a consistent environment



If you are using Docker, it is best to build your application in a managed environment to provide isolation.



We should avoid creating applications locally and adding them to the registry.



Otherwise, the packages that you installed in your local environment may affect the consistency of the image. Probably no one wants to be in this situation, since it compromises one of the main advantages of Docker - consistent execution across different environments.



Using multi-stage assemblies to remove dependencies



It is recommended that you use a multi-stage application deployment method.



This eliminates the use of assembly dependencies in a running container.



We can build the application using a unique build image that has dev dependencies and move the compiled binaries into a separate container image to run it.



# Stage 0, "build-stage", based on Node.js, to build and compile the frontend
FROM node:13.12.0 as build-stage
WORKDIR /app
COPY package*.json /app/
RUN npm install
COPY ./ /app/
RUN npm run build
# Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx
FROM nginx:1.15
COPY --from=build-stage /app/build/ /usr/share/nginx/html
      
      





There are two separate steps in the above Dockerfile. Stage 0 is used to build a node application from the original node image, and stage 1 is used to copy the assembled files from the build image to the web server (Nginx) image that ultimately serves the application.



Conclusion



That's all I wanted to tell you. If you're new to Docker, I recommend trying these practices when building your first image. This will help you understand the topic more deeply and will allow you to use Docker effectively from day one.



If you know of other cool practices, share in the comments. Thanks for reading!



All Articles