I’m trying to get a little Rails application ready for production, and just for fun I’m trying to make the image a little slimmer than it currently is (860 MB).

There is a official docker image of ruby with alpine (ruby:alpine), but because of the libraries that rails uses (with native bindings), it is a little more challenging than just referencing that image on the top of the Dockerfile.

Solving the issues

I added FROM ruby:2.4.1-alpine at the top of my Dockerfile and tried to create the image. The first problem I faced was with myql2. For the mysql2 gem to work on Alpine it is necessary to have a compiler (build-base) and MySQL development libraries (mariadb-dev). I added this to my Dockerfile:

1
2
3
4
RUN apk add --update \
  build-base \
  mariadb-dev \
  && rm -rf /var/cache/apk/*

The next error was about sqlite3, which I use for my tests. To fix it we need to install sqlite-dev:

1
2
3
4
5
RUN apk add --update \
  build-base \
  mariadb-dev \
  sqlite-dev \
  && rm -rf /var/cache/apk/*

This was enough for Docker to be able to build the image, but when I tried to run it I got an error related to therubyracer, a V8 JavaScript interpreter used by Rails to compile your JS assets (among other things). To solve this issue I removed therubyracer from my Gemfile:

1
2
# Removed this line
gem 'therubyracer', platforms: :ruby

And installed node on the image. Node is able to compile my JS assets but doesn’t do everything therubyracer does. My app didn’t need any of the extra functionality, so this was enough:

1
2
3
4
5
6
RUN apk add --update \
  build-base \
  mariadb-dev \
  sqlite-dev \
  nodejs \
  && rm -rf /var/cache/apk/*

Next was a Railtie complaining about tzinfo-data not being present. This issue was easily fixed by installing tzdata:

1
2
3
4
5
6
7
RUN apk add --update \
  build-base \
  mariadb-dev \
  sqlite-dev \
  nodejs \
  tzdata \
  && rm -rf /var/cache/apk/*

The final result

After this I was able to run my Rails application successfully. The size of the new image is 580 MB (67% the size of the original one). I was actually expecting a bigger gain, but given that it was not really that hard, I’ll call it a gain. My final Dockerfile looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
FROM ruby:2.4.1-alpine

RUN apk add --update \
  build-base \
  mariadb-dev \
  sqlite-dev \
  nodejs \
  tzdata \
  && rm -rf /var/cache/apk/*

RUN gem install bundler

# First copy the bundle files and install gems to aid caching of this layer
WORKDIR /myapp
COPY Gemfile* /myapp/
RUN bundle install

WORKDIR /myapp
COPY . /myapp

CMD /bin/sh -c "rm -f /myapp/tmp/pids/server.pid && ./bin/rails server"
[ linux  automation  docker  productivity  rails  ruby  ]
Instrumenting an Istio Cluster With Jaeger Tracing
Monitoring Kubernetes Resources with Fabric8 Informers
Resource Management in Kubernetes - Requests and Limits
Kubernetes ConfigMaps
Command Line Efficiency With Tmux