Lessons learned from first attempt of dockerizing Ruby on Rails app
Docker impressed me since first time I’ve heard about it on local development conference. Few years passed while I’ve tried this by myself. Here is a story of my experience with Docker and related instruments.
As you may know, Docker is a software container platform and could be useful in three basic options: for local development environments, for apps running in isolated containers, for entire infrastructure built on top of containers clusters with microservices approach. Nowadays every option has a place for application but for me the second and the third looked like the most interesting.
Let’s dockerize it!
My idea for Docker application was to create an image for APIQ CMS system that our team open sourced few months ago. I’ve started with simple Dockerfile that was pretty common for any Rails project and could be found on Docker Hub. It looked really simple but it didn’t work in my case. If your project is a little bit more complex, things go wrong and hard.
Lesson 1: gems with C bindings cause problems
APIQ has a dependency on well-known
therubyracer (and actually
libv8) gem. If you ever worked on dockerizing Rails app with such dependency, you know what I’m talking about. After spending some time on googling, I’ve came to this:
ENV LIBV8_VERSION 188.8.131.52
RUN apk --update --no-cache add --virtual build-deps build-base python postgresql-dev nodejs g++; \
bundle config build.libv8 --enable-debug && \
LIBV8_VERSION=$LIBV8_VERSION bundle install --without development test && apk del build-deps
Lesson 2: embed assets in image or precompile
Precompiling assets with Rails Assets pipeline can take a lot of time. You can keep your image as small as possible and not pack assets into image (but then you need to call assets precompiling task on launching concrete container). I’ve chosen to precompile them on image build stage but faced with another problem: lack of database :-) Yes, this is well-known fact that rake (rails) tasks load entire environment so you need a database. I’ve found a workaround in using
activerecord-nulldb-adapter gem that does the trick.
RUN bundle exec rails kms:install:migrations \
&& SECRET_TOKEN="$(bundle exec rails secret)" DB_ADAPTER=nulldb bundle exec rails assets:precompile
As you may notice, you’ll need some stub for SECRET_TOKEN as well. Your
database.yml could look like this then:
adapter: <%= ENV['DB_ADAPTER'] ||= 'postgresql' %>
Lesson 3: image size rage
If you’re going to have your Docker image as small as possible, you’d like to try these options:
- choosing the right base image (for example, based on Alpine Linux
- minimizing number of intermediate layers (by concatenating
RUNcommands installing necessary packages especially)
- installing only really necessary OS packages
Lesson 4: Docker for Mac
As I was on the way of building my image, I’ve noticed my disk space is going to zero after several build attempts. I was totally surprised that this was a Docker for Mac issue that is still not resolved (https://github.com/docker/for-mac/issues/371). The simplest working solution for me was resetting Docker to factory defaults.
And finally… Going public
After all these things that I’ve learned when was trying Docker, I’ve finally pushed new image to Docker Hub. And that was the easiest step: you just need to wait until image will get uploaded!
I hope these lessons will help anyone faced with similar problems to save their time and continue digging into the world of Docker. :-)