Installation of bundle takes a majority of the time in Dockerfile for a Rails build.
Here is the standard and shortened version of a Dockerfile for a Rails application.
FROM ruby:3.1.2
RUN gem install bundler:2.3.7
WORKDIR /app
COPY Gemfile Gemfile.lock ./
RUN bundle install
COPY . .
The Dockerfile uses build cache which separates out Gemfile
and Gemfile.lock
before copying the source code.
This works great as Gems are only installed for the first time when the build is running.
Unlike non-dockerized environments, when there are changes made to the Gemfile, a gem is changed, or new ones added/removed, all the gems are re-installed from scratch.
Using Cache Image
RUN gem install bundler:2.3.7
WORKDIR /app
# Copy the gems from a dedicated cache image
COPY --from saeloun:rails7:gem-cache /usr/local/bundle /usr/local/bundle
COPY Gemfile Gemfile.lock ./
RUN bundle install
COPY . .
It copies the /usr/local/bundle
directory from the saeloun:rails7:gem-cache
image to our build.
With COPY --from
, Docker can copy files from an existing registry to the current build.
By doing so, it is ensured that the Bundle does not start from scratch
and has a cache of all the previously used gems.
The only problem with this that an image has to exist in the registry for this to work.
The issue with this approach is it will fail, if the initial build image does not exist.
MultiStage with Gem Caching
We can use MultiStage-build to refine the dockerfile further and pass the registry as docker build argument.
ARG BASE_IMAGE=ruby:3.1.2
ARG CACHE_IMAGE=${BASE_IMAGE}
# Build stage for the gem cache
FROM ${CACHE_IMAGE} AS gem-cache
RUN mkdir -p /usr/local/bundle
# Image with Bundler Installed
FROM $BASE_IMAGE AS base
RUN gem install bundler:2.3.7
WORKDIR /usr/src/app
# Copy gems from a gem-cache build stage
FROM base AS gems
COPY --from=gem-cache /usr/local/bundle /usr/local/bundle
COPY Gemfile Gemfile.lock ./
RUN bundle install
# Get the source code in place
FROM base AS deploy
COPY --from=gems /usr/local/bundle /usr/local/bundle
COPY . .
The build stage has 4 Steps
-
gem-cache
creates a directory for Gem Cachingbase
installs bundlergem
copies the gems from the existing image and runs bundlerdeploy
add the source code
If CACHE_IMAGE is not set, the content of /usr/local/bundle
directory will be empty
and we won’t be able to copy any gems.
If it is set to an image with gems,
those will be copied over.
The image can now be built using
docker build .
After tagging, CACHE_IMAGE
can be set as follows-
docker build . --build-arg CACHE_IMAGE=saeloun:rails7:gem-cache app