疑問
以下はRails7.2でRailsプロジェクトを作成した時のデフォルトのDockerfileの内容の一部です。
# Install application gems
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
bundle exec bootsnap precompile --gemfile
# Copy application code
COPY my_app .
この部分、何も考えず自分で作成するとしたら以下のように書きたくなります。
COPY my_app .
RUN bundle install && \
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
bundle exec bootsnap precompile --gemfile
なぜこのように段階を踏んで記述するのでしょうか?
今回はその理由調べてまとめました。
理由 1: キャッシュの活用によるビルドの高速化
Dockerではコマンド毎にCOPYやRUNコマンド毎にレイヤーとして情報をキャッシュする仕組みがあります。
キャッシュを利用するかどうかの判定はその時点で差分があるかどうかです。
もし私が書きたいように記述したとするとソースファイルの変更があるたびにキャッシュを利用せずに、bundle install
をやり直すことになり、ビルドにかなりの時間を割くことになります。
しかし、GemfileとGemfile.lockのみをコピーすることでこれらのファイルに変更がない限りビルド時にbundle install
を行うことになり効率よくビルドすることができるようになります。
理由 2: 依存関係とソースコードの分離
依存関係とソースコードを分離することでデバッグがやりやすくなります。
もし私が書きたいように記述するとソースファイルに問題がある場合のエラー時に原因をGemの依存関係に問題があるのか、それともソースコードに問題があるのかといった原因を切り分けることができます。
また、ソースコードに原因があった場合に、ソースコードを修正しただけでも再びbundle install
を実行することになります。
今回のことで学んだこと
- Dockerfileではコマンド毎にその時点でのレイヤーをキャッシュして差分がない場合はキャッシュしているレイヤーを利用してビルドを行う
- Dockerfileで生成したイメージは全てのレイヤーの情報を保持しているためレイヤーが増えるとイメージが大きくなるのでコマンドはまとめる方がいい