背景
docker-composeでrails環境を構築したが起動せず。
gemが無いと言われているのでdocker-compose run 'コンテナ' bundle install
の実行で解決。
けどなんでDockerfile内でbundle install
しているのに再度bundle install
しないといけないんだろうと疑問に思ったので調査してみました。
先に結論
volumeに古い情報が保存されており、最新の情報がvolumeに上書きされてしまう。
エラー再現例
構築時のファイル例
FROM ruby:2.6.3
RUN apt-get update -qq && \
apt-get install -y build-essential \
libpq-dev \
postgresql-client &&\
rm -rf /var/lib/apt/lists/*
# install nodejs
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - \
&& apt-get install -y nodejs
# install yarn
RUN apt-get update && apt-get install -y curl apt-transport-https wget && \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
apt-get update && apt-get install -y yarn
ENV APP_HOME /my_app
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
ADD Gemfile Gemfile
ADD Gemfile.lock Gemfile.lock
RUN bundle install
ADD . $APP_HOME
version: '3'
services:
web:
build: .
ports:
- "3000:3000"
command:
[ "bash", "-c", "rm -f tmp/pids/server.pid; RAILS_ENV=development bundle exec rails s -b 0.0.0.0" ]
volumes:
- .:/my_app
# volumeを使用してbundle installしてきたものを永続化
- bundle:/usr/local/bundle
volumes:
bundle:
source 'https://rubygems.org'
gem 'rails', '6.0.0'
空のGemfile.lock
$ touch Gemfile.lock
よくある構築コマンド例
# railsプロジェクトを作成
$ docker-compose run web bundle exec rails new . --force --skip-bundle
# Dockerfileからイメージを作成
$ docker-compose build
# コンテナ(rails)の起動
$ docker-compose up
を実行すると
web_1 | bundler: failed to load command: rails (/usr/local/bundle/bin/rails)
web_1 | Bundler::GemNotFound: Could not find gem 'sqlite3 (~> 1.4)' in any of the gem sources listed in your Gemfile.
...
と出て起動しない。
コンテナに入ってgemを確認してみると
$ docker-compose run web ls /usr/local/bundle/gems
actioncable-6.0.0 erubi-1.8.0 rack-test-1.1.0
actionmailbox-6.0.0 globalid-0.4.2 rails-6.0.0
actionmailer-6.0.0 i18n-1.6.0 rails-dom-testing-2.0.3
actionpack-6.0.0 loofah-2.2.3 rails-html-sanitizer-1.2.0
actiontext-6.0.0 mail-2.7.1 railties-6.0.0
actionview-6.0.0 marcel-0.3.3 rake-12.3.3
activejob-6.0.0 method_source-0.9.2 sprockets-3.7.2
activemodel-6.0.0 mimemagic-0.3.3 sprockets-rails-3.2.1
activerecord-6.0.0 mini_mime-1.0.2 thor-0.20.3
activestorage-6.0.0 mini_portile2-2.4.0 thread_safe-0.3.6
activesupport-6.0.0 minitest-5.11.3 tzinfo-1.2.5
builder-3.2.3 nio4r-2.5.0 websocket-driver-0.7.1
concurrent-ruby-1.1.5 nokogiri-1.10.4 websocket-extensions-0.1.4
crass-1.0.4 rack-2.0.7 zeitwerk-2.1.9
docker-compose build
のときにbundle install
したはずなのにgem 'rails'
でインストールしたgemしか入っていない。(rails new
で更新されたGemfileのgemがインストールされていない)
なぜ起きるのか
下記条件で発生します。
-
bundle install
してきた内容をvolumeを使用してデータを永続化をしている。 - rails newで--skip-bundleしている。
ポイント
-
docker-compose build
ではvolumeと紐付かない -
docker-compose run
はvolumeと紐づく。
原理
# 初回のbuildが走る。bundle installで ”railsのみ” がコンテナ内の/usr/local/bundleにインストールされる。
# volumeと紐づくので ”railsのみ” がvolumeに保存される。
$ docker-compose run web bundle exec rails new . --force --skip-bundle
# 新しいGemfileのgemがコンテナ内の/usr/local/bundleにインストールされる。
# volumeと紐づかないのでvolumeは更新されない。
$ docker-compose build
# volumeと紐付いたタイミングで/usr/local/bundleがvolumeに上書かれてしまう。
$ docker-compose up
対応
-
docker-compose run web bundle install
でvolumeを更新する。 - volumeを一回削除し再度作成することによって、volumeの中身を/usr/local/bundleと同じにする。
-
rails new
する時に--skip-bundle
を使用しない。
疑問
docker-compose run 'コンテナ' bundle installをどうせ走らせるのならDockerfileからbundle installを削除していいのでは。
イメージとdocker-composeを1セットと考えるとそう思いますが、同じイメージを使ってvolumeを使わないパターンもあり得るのでbundle install
をDockerfileから削除するのが必ずしも正しいわけではないのかなと感じました。
開発する上で下記を意識すれば問題ないかと思います。
- gemをvolumeで管理しない:
docker-compose build
でgemを管理する。 - gemをvolumeで管理する:
docker-compose run 'コンテナ' bundle install
でgemを管理する。(docker-compose build
でbundle install
が走るのは我慢する?)
さいごに
Qiitaとかにあるチュートリアルをそのまま実行して起動できなくてハマった方もいると思うので、少しでも参考になればと思います。
間違えや不足などあればご指摘頂ければ嬉しいです。