How to dockerize
a Ruby on Rails app
with a small image
This week I attended a Docker course with Daniel Romero (a.k.a infoslack).
The course was great! The teacher challenged us to dockerize a spree app with a constraint: the final image could not have more than 400mb.
It may not sound so challenging, but there are a few initial problems to achieve it:
- the official Ruby images are huge. For example, the image ruby:2.2.2 is 704mb.
- spree has a lot of dependencies to run, this requires disk space.
- it is harder to install app dependencies while using a hipster distro or base image.
But yeah, challenge accepted!
Let’s get started
I created a vanilla rails app with Postgres setup:
rails new my_store -d postgresql
As I said on the introduction, I already knew that the full Ruby official full image would not be small, so that was not the way to start.
I found an official slim version, the size: 263.6mb. Less than our minimum requirement, so my first Dockerfile was something like this:
The result was an image of 770mb, far from what we need here!
Enter Alpine Linux
Alpine Linux is a lightweight distribution that is 5mb sized. Small enough and very promising, let’s use it!
The problem is that, to be small it does not come with a “full” ruby installation, it is divided among a lot of packages: ruby, ruby-irb, ruby-bundler to name a few.
After digging around a lot, I came with all package dependencies needed:
The result was an image of 482mb! Much closer to our target!
Note that we are installing the packages needed to install the gems and after that we are uninstalling them in another RUN command.
The final trick
But that was not enough, here comes the trick:
The result: 197MB image! \o/
Did you notice the difference?
I am adding the packages needed to compile and install all the gems of our spree app and uninstalling them in the same RUN command.
Each line of the of the Dockerfile makes a commit (creates a layer) for the resulting image. Since we are removing the packages before the commit, there is no trace of them in the final image size.
This tricky I learned in this blog post with a similar approach with Go.
Since Go is a compiled language, a lot of packages can be removed after compiling what is needed.
Ruby is a script language and we need to keep some packages installed so our app can run properly.
Hope this helps someone to get smaller Ruby containers running :)
The question is, is there a way to make it even smaller?