A big problem that we face when deploying Docker into production is the image size. Large images take longer to download, consume much of your cloud network traffic quota, cost more money to be stored on the repository and don’t bring any good value.
In most situations, when we create a Docker image, we add steps and dependencies that sometimes we don’t need in the final image that will run in production.
This build generated an image with almost 1GB!!! 😱.
This image has some unnecessary stuff, like node but yarn (we only need them to precompile the assets but not to execute the application itself).
Multi-Stage build
Docker introduced the concept of Multi-Stage build in version 17.05. This build technic allows us to split our Dockerfile into several statements FROM. Each statement can use a different base image and you can copy artifacts from one stage to another, without bringing stuff that you don’t want in the final image. Our final image will only contain the build wrote in the last stage.
Now we have a Dockerfile divided into two stages. Pre-build and Final-Build.
In the pre-build stage we install node and yarn, all dependencies and precompile the assets. In the final stage, we use an alpine image (which is very small) with ruby, we install only the necessary dependencies to run the application and we then copy the libraries and assets generated in the build-stage with the following command:
Doing the build with this Dockerfile, we have now a 562MB image.
We have already reduced almost half the image size, but can we reduce it further?? 🤔
Yes. We can do some actions to reduce more this image.
Removing unnecessary files
We can delete files that are not necessary from the image, like cache and temporary files used by the installed libraries. We can add a .dockerignore file, telling the build what not to send to the image.
In this new Dockerfile, we added this part that removes caches and temporary C files used to build the libraries:
We also included our .dockerignore to tell the build process the files that we don’t want in the image:
With these two steps, now our image has 272MB.
We can reduce it even more. For production, we don’t need test folders, npm raw folder (they are already included on the asset pipeline), no precompiled assets and caches.
To remove this files, we can include a strategy of passing an argument to build (we will call it: to_remove)
In this argument, we will pass all the files that we don’t want in production:
Notice the — build-arg to_remove=”spec node_modules app/assets vendor/assets lib/assets tmp/cache”. These are the folders that we want to remove from our build process. We don’t need them to run in production.
Removing these files, now we have an image with 164MB, almost 6 times smaller than the original one.