Rails: Dockerマルチステージビルドでgemをキャッシュする(翻訳)
Railsのビルドでは、Dockerfileのbundle installにかかる時間が大半を占めます。
以下は、あるRailsアプリケーションの標準的なDockerfileを簡略化したものです。
FROM ruby:3.1.2
RUN gem install bundler:2.3.7
WORKDIR /app
COPY Gemfile Gemfile.lock ./
RUN bundle install
COPY . .
このDockerfileでは、ソースコードをコピーする前にビルドキャッシュを使ってGemfileとGemfile.lockを分離しています。
gemがインストールされるのは最初のビルドだけなので、この方法は非常に有効です。
非Docker環境と異なるのは、Gemfileやgemを変更したりgemを追加・削除したりすると、すべてのgemが最初から再インストールされることです。
キャッシュイメージを使う
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 . .
上は、saeloun:rails7:gem-cacheイメージの/usr/local/bundleディレクトリを自分たちのビルドにコピーします。
COPY --fromを指定すると、Dockerはファイルを既存のレジストリから現在のビルドにコピーできます。これにより、Bundlerの実行をやり直さなくなり、前回使ったすべてのgemがキャッシュされるようになります。
唯一の問題は、イメージがそのレジストリ内に存在していないと動作しないことです。この方法は、初期のビルドイメージが存在しない場合は失敗します。
マルチステージでgemをキャッシュする
マルチステージビルドを使うと、Dockerfileをさらに改善してレジストリをdocker buildの引数として渡せるようになります。
ARG BASE_IMAGE=ruby:3.1.2
ARG CACHE_IMAGE=${BASE_IMAGE}
# gemキャッシュ用のビルドステージ
FROM ${CACHE_IMAGE} AS gem-cache
RUN mkdir -p /usr/local/bundle
# Bundlerインストール済みのイメージ
FROM $BASE_IMAGE AS base
RUN gem install bundler:2.3.7
WORKDIR /usr/src/app
# gemキャッシュ用のビルドステージからgemをコピーする
FROM base AS gems
COPY --from=gem-cache /usr/local/bundle /usr/local/bundle
COPY Gemfile Gemfile.lock ./
RUN bundle install
# ソースコードを配置する
FROM base AS deploy
COPY --from=gems /usr/local/bundle /usr/local/bundle
COPY . .
このビルドステージには以下の4つのステップがあります。
gem-cache: gem-cache用のディレクトリを作成するbase: Bundlerをインストールするgem: gemを既存のイメージからコピーしてBundlerを実行するdeploy: ソースコードを追加する
CACHE_IMAGEが設定されていないと/usr/local/bundleの内容が空になり、gemをコピーできなくなります。CACHE_IMAGEにgem入りのイメージが設定されていれば、gemはコピーされます。
これで、以下を実行してイメージをビルドできるようになります。
docker build .
タグが設定された後は、CACHE_IMAGEを以下のように設定できます。
docker build .  --build-arg CACHE_IMAGE=saeloun:rails7:gem-cache app
      
概要
元サイトの許諾を得て翻訳・公開いたします。