docker-compose run web bundle installでGemをインストールした後に再度buildが必要な理由

前提

https://docs.docker.com/samples/rails/ を参考にDocker環境でRailsを起動

問題

fakerをGemfileに追記後、docker compose run web bundle install, docker compose upすると以下のエラーが出る。

docker-rails-sample-web-1  | /usr/local/lib/ruby/3.1.0/bundler/definition.rb:481:in `materialize': Could not find faker-2.19.0 in any of the sources (Bundler::GemNotFound)

https://docs.docker.com/samples/rails/#rebuild-the-application を読むとgemをGemfileに追記した後にdocker compose run web bundle install,docker compose up --build する必要がある。

なぜbuildする必要があるのか理解できなかったので整理する。

メモ

  • イメージとは -> コンテナの土台となる動作環境テンプレート

  • Dockerfileとは -> カスタムイメージを作るためのファイル

  • Buildとは -> Dockerファイルからイメージを作ること

docker compose run web bundle install

runコマンドについて

1 つのサービスに対して、1 コマンドだけ実行します。 runによって指定したコマンドは、定義されたサービス設定に基づいて生成された新たなコンテナー内において実行されます。

https://matsuand.github.io/docs.docker.jp.onthefly/compose/reference/run/

ポイントは新たなコンテナー内において実行されるということ。

bundle installでgemが追加される環境はrunコマンドによって作成されたコンテナーに限定される。

docker compose up

コンテナの作成、起動を行う

docker compose build

DockerFileを元にサービスのビルドを実行する。

DockerホストのGemfile, Gemfile.lockを元にbundle installを実行する

# 一部抜粋
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install

エラーが起きるまでの流れ

  1. Dockerホスト(Mac)のGemfileに追記すると、ボリューム先のGemfileも更新される
  2. docker compose run web bundle install を実行すると、コンテナが新たに作成され、そのコンテナでbundle install が実行され、faker gemがインストールされる。 またコンテナー、Dockerホスト のGemfile.lockが更新される
  3. docker compose up を実行すると、fakerをインストールしていないイメージを元にコンテナが作成されるが、Gemfile, Gemfile.lockにはfakerの記述あるので、エラーになる

結論

再度buildしイメージを更新しないとdocker compose up で作成されるcontainerの環境にgemがインストールされない。

回避策としてgem用のvolumeを作って永続化すれば、再buildしなくて済みそうです。

https://qiita.com/neko-neko/items/abe912eba9c113fd527e

おまけ buildした時に必ずbundle installが走るわけではない

  1. docker compose build
  2. 再度docker compose build するがgemのinstallのログは吐かれていない
  3. コンテナのgemのフォルダに移動して作成日時を確認すると、1回目のbuildの時刻と一致する

キャッシュしていることが原因。docker compose build -no-cache とすると上記手順の2でもgemがinstallされる。(インストールされるgemは変わらないので意味はないです。)