Speeding up Docker on Ruby projects - an experimental approach
— March 4, 2016
It has been three days since I started exploring Docker with Rails, spending most of my time trying to figure out the development and production workflows (including setting up a load balancer with nginx, Registrator, and Consul because I wanted zero-downtime deployments).
Let’s take a look at this command:
Our workflow is slowed down tremendously since we’re using Docker.
Due to the nature above, any Ruby project can be slow on Docker because whenever the contents of the Gemfile changes, even by one or two gems, it has to run bundle installall over again during Docker’s build process. It may be fine in production, but when we’re in development mode, constantly altering the gems, we’ll end up spending more time waiting for the image to build.
Here are a few approaches that I took to fix that:
First approach: utilizing Docker’s cache
Now, that’s caching from Docker, but once we have invalidated the hash of the Gemfile, the cache is busted! There’s no way we could avoid this since we are constantly adding or removing gems during development.
Second approach: Gemfile hack
This approach is very similar to the first one. This time, we’re adding multiple Gemfiles as we add gems, ensuring that the actual Gemfile’s cache is not busted (since this usually contains the most number of gems).
This definitely wasn’t a good solution – too much extra work was involved.
Third approach: Data-only containers
Since data within containers are not persisted on each build, we’ll try to extract the gems’ data into a different container and mount that data container with our app. By default, gems are installed in /usr/local/bundle. We will now mount a custom volume to that directory through a data-only container.
Gemfile
docker-compose.yml
start.sh
All we need to do is:
If we want to avoid typing those commands, we could use aliases.
.bash_profile
The rm flag is needed so that our containers are removed when they are done.
When we have done so, we could just do the following:
Note: All the three approaches above are for development mode only. Although the typical workflow for production is to upload to Docker Hub’s private registry to build, however, I feel that we can also apply this to production, but I’ve not tried that yet.