rubyのdockerイメージで2つ以上のGemfileを使えない問題


officialのRuby2.5.1のイメージを使って、bundlerで2つ以上のGemfileを使おうとした際に遭遇した問題。
bundlerにすでにissueとして上がっているが、日本語の情報が少ないのでまとめておく。
https://github.com/bundler/bundler/issues/6154

問題の再現条件

ruby:2.5.1のイメージから初めて以下のようにDockerfileとGemfileを作り、Dockerイメージをビルドする。

Dockerfile
FROM ruby:2.3

ADD Gemfile /tmp/bundle/
RUN cd /tmp/bundle && bundle install
Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

# gem "rails"
gem "json"
$ docker build -t rb_test .

(最初から docker run -it ruby:2.5.1 /bin/bash として対話的環境の中で同等の作業をすれば良いと思われるかもしれない。しかし、この方法では問題は再現しない。"/etc/profile"が読まれるとこの問題は回避できるようだ。)

ここで2つ目のRubyプロジェクトがあるとして、新しいGemfileを作ってインストールしてみる。

$ docker run -it rb_test /bin/bash

コンテナ内にログインできるので、ここで新たにGemfileを作ってbundleを実行してみる。

$ mkdir -p ~/proj
$ cd ~/proj
$ bundle init
$ # 適当なGemfileを作って、中身に適当なgemを加える
$ echo 'gem "pry"' >> Gemfile
$ bundle

するとbundle自体は正常終了するのだが、/tmp/bundle/Gemfileに書かれたgemがインストールされてしまう。
projに移動した後も/tmp/bundle/Gemfileの方を参照し続けてしまうようだ。
当然bundle exec pryを実行しても必要なgemがないのでエラーになる。

問題の調査

どうやらこれはRubyのdockerイメージで、かつbundler1.16の場合に発生する問題のようだ。

rubyのdockerのイメージではgemを「グローバル」にインストールする方式が取られている。
詳細は理解していないが、この仕様がbundler1.16との相性が悪い模様。

Dockerfile
# install things globally, for great justice
# and don't create ".bundle" in all our apps
ENV GEM_HOME /usr/local/bundle
ENV BUNDLE_PATH="$GEM_HOME" \
    BUNDLE_BIN="$GEM_HOME/bin" \
    BUNDLE_SILENCE_ROOT_WARNING=1 \
    BUNDLE_APP_CONFIG="$GEM_HOME"

2.5.1のdockerfileはこちら

何が起きているか詳細にみてみる。
まずイメージを起動した直後、bundleコマンドは /usr/local/bin にインストールされている。

しかしながら、一度bundleを実行した後は以下のようになる。

$ which bundle   # /usr/local/bundle/bin/bundle

/usr/local/bundle/bin/bundleというファイルが作られて、その中身をみていくと次のような記述がある。

/usr/local/bundle/bin/bundle
  def gemfile
    gemfile = ENV["BUNDLE_GEMFILE"]
    return gemfile if gemfile && !gemfile.empty?

    File.expand_path("../../../../../tmp/bundle/Gemfile", __FILE__)
  end

なんと、/tmp/bundleのGemfileのパスがハードコードされている(!!!)
以後bundleコマンドを実行しても、この新しくできたbundleコマンドが参照され、カレントディレクトリのGemfielが参照されないという罠だった。
(これに気づくまであれやこれや調べてかなり時間を使った

回避方法

2018年5月現在、まだgithubのissueはオープンなまま。
とりあえずgithubのissueでも議論されている暫定的な回避策としては環境変数として export BUNDLE_GEMFILE=./Gemfileを指定する方法がある。

通常bundleを実行した時にはカレントディレクトリにあるGemfileが参照される。(カレントにGemfileがない場合は親ディレクトリを探しにいく)
しかし、一度bundleを実行したあとにGemfileのパスが決め打ちされるような状況になるので、環境変数BUNDLE_GEMFILEを使って無理やりカレントのものを参照することで回避できる。

なぜbundleがbinの下にbundleコマンドを自ら作るのかまだ把握していないので、もし知っている方がいれば教えてください。bundleコマンド自身のバージョンを固定するため(?)