Help us understand the problem. What is going on with this article?

Docker Compose + Railsでイメージ内でbundle installしているはずなのにgemが無いとエラーがでる。

背景

docker-composeでrails環境を構築したが起動せず。
gemが無いと言われているのでdocker-compose run 'コンテナ' bundle intallの実行で解決。
けどなんでDockerfile内でbundle installしているのに再度bundle installしないといけないんだろうと疑問に思ったので調査してみました。

先に結論

volumeに古い情報が保存されており、最新の情報がvolumeに上書きされてしまう。

エラー再現例

構築時のファイル例

Dockerfile
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
docker-compose.yml
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:
Gemfile
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-compsoeを1セットと考えるとそう思いますが、同じイメージを使ってvolumeを使わないパターンもあり得るのでbundle installをDockerfileから削除するのが必ずしも正しいわけではないのかなと感じました。
開発する上で下記を意識すれば問題ないかと思います。

  • gemをvolumeで管理しない:docker-compose buildでgemを管理する。
  • gemをvolumeで管理する:docker-compose run 'コンテナ' bundle installでgemを管理する。(docker-compose buildbundle installが走るのは我慢する?)

さいごに

Qiitaとかにあるチュートリアルをそのまま実行して起動できなくてハマった方もいると思うので、少しでも参考になればと思います。
間違えや不足などあればご指摘頂ければ嬉しいです。

hokita222
デグー飼いwebエンジニア rails、golang、設計、vim
enigmo
株式会社エニグモは、ソーシャルショッピングサイト「BUYMA(バイマ)」を運営する会社です。
https://www.enigmo.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした