Jack Moore

Email: jack(at)jmoore53.com
Project Updates

Rails Production Dockerfile 2.0

15 Aug 2019 » containers, docker, commandcenter

This may be the only post with links at the top, but check out Rails Skeleton on my Gitlab Page for a secure, updated, and easy to manage rails starter kit with Docker Support and Gitlab CI/CD!

Building a Production Rails Docker Image

There have been many road bumps along the way while building this docker file and the docker image, but I finally created a dockerfile that works for production rails!

# Base
FROM ruby:2.6.3-alpine3.10 as builder

# Define basic environment variables
ENV NODE_ENV production
ENV RAILS_ENV production
ENV RAILS_LOG_TO_STDOUT true
ENV RAILS_SERVE_STATIC_FILES true

# Install alpine packages
RUN apk add --no-cache \
  build-base \
  busybox \
  ca-certificates \
  cmake \
  curl \
  git \
  tzdata \
  gnupg1 \
  graphicsmagick \
  libffi-dev \
  libsodium-dev \
  openssh-client \
  mysql-dev \
  mysql-client \
  tzdata \
  libssl1.1 --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main \
  libuv --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main \
  musl --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main \
  libgcc --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main \
  libstdc++ --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main \
  nghttp2-libs --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main \ 
  zlib --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main

# Add Edge JS Packages
RUN apk add --no-cache nodejs-current --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community
RUN apk add --no-cache nodejs --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main
RUN apk add --no-cache npm --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main
RUN apk add --no-cache yarn --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community

# Define WORKDIR
WORKDIR /app

# Use bunlder to avoid exit with code 1 bugs while doing integration test
RUN gem install bundler -v 2 --no-doc

# Copy dependency manifest
COPY Gemfile Gemfile.lock /app/

# Install Ruby dependencies
RUN bundle update --bundler
RUN bundle install --jobs $(nproc) --retry 3 --without development test \
      && rm -rf /usr/local/bundle/bundler/gems/*/.git /usr/local/bundle/cache/


# Remove Older Assets
RUN rm -rf public/packs node_modules/

ARG RAILS_KEY
ENV RAILS_MASTER_KEY=${RAILS_KEY}
RUN rails webpacker:install
RUN rails app:update:bin
RUN rails webpacker:info

# Copy source code
COPY . /app/

# Copy JavaScript dependencies
COPY ./package.json yarn.lock /app/

# Compile Assets
ARG RAILS_KEY
ENV RAILS_MASTER_KEY=${RAILS_KEY}
RUN yarn install
RUN RAILS_ENV=production rake assets:precompile

# Slim down Image
FROM ruby:2.6.3-alpine3.10
RUN apk add --update --no-cache \
  libssl1.1 --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main \
  openssl \
  tzdata \
  mysql-dev \
  mysql-client \
  nodejs
COPY --from=builder /usr/local/bundle/ /usr/local/bundle/
COPY --from=builder /app/ /app/
COPY --from=builder /app/public /app/public
ENV RAILS_LOG_TO_STDOUT false
WORKDIR /app
EXPOSE 3000
ARG RAILS_KEY
ENV RAILS_MASTER_KEY=${RAILS_KEY}
CMD puma -C config/puma.rb -e production

10,000ft Overview of the Dockerfile

This massive Dockerfile has so much going on in it, it’s only approprite to give a brief overview before diving in to specific pieces.

The image basically starts with the base alpine image to keep everything slim. It then goes on to define the environment variables, and install the correct packages on the base image. I ran into a lot of trouble with the node packages and ensuring they were up to date and configured properly, so I pulled the node repos from the edge packages to keep them current. From there I defined the work directory for the dockerfile which is pretty standard for building any image. After I defined the work directory I started working on the Ruby aspects of the dockerfile ensuring all the gems were up to date and installed in the image.

After having this setup, I set the arguments up for the RAILS_KEY and set the environment variables so I could compile all the assets and install webpacker correctly on the image. I updated everything for rails webpacker and then checked to confirm it was installed with the correct versions. After installing webpacker, I copied the rest of the /app files, the package.json file and then yarn install-ed the rest of the javascript packages. I then compiled them with a production rake assets:precompile and everything with the image was ready to go!

But that wasn’t all! I needed it to be slimmer. This image was just too big!

Slimming down the Image

Slimming an image was a difficult concept to grasp initially, but after reading the docs on the Docker site, it wasn’t too difficult to put the pieces together and slim down the image above.

At the root of slimming down any image is taking a “bigger” image and only using the pieces that are required from the larger image.

With this image and this dockerfile, I took the basics from the builder image and built only the pieces that I needed. I only copied only the parts that were necessary to run a production rails application. After taking these pieces, I started the server and everything was looking good!

Building the Image

docker build -t cc/builder_prod --build-arg RAILS_KEY=$MASTER_KEY . -f docker.builder.Dockerfile
© Jack Moore