TL;DR
- Docker 公式の ruby イメージでセットされている環境変数
BUNDLE_APP_CONFIG
は DevContainer 内での開発において邪魔になることがある -
BUNDLE_APP_CONFIG
の影響を無くしたい場合はdevcontainer.json
に以下を記述すると良い
{
// ...
"remoteEnv": {
"BUNDLE_APP_CONFIG": null
},
// ...
}
厄介な BUNDLE_APP_CONFIG
VSCode Remote - Containers での Ruby 開発では大抵の場合、Docker 公式の ruby イメージ をベースイメージにして DevContainer を作ることになる1かと思いますが、このイメージには厄介な問題があります。
それが環境変数 BUNDLE_APP_CONFIG
です。
Dockerfile 内で ENV
命令によりデフォルトで /usr/local/bundle
に設定されています。
BUNDLE_APP_CONFIG
は bundler のアプリケーションローカルな設定 (bundle config set --local
した際に作られる設定) をどこに保存するかを指定するための環境変数です。
この環境変数が設定されていない場合、bundler はアプリケーションルートの .bundle/
以下に設定を作成します。
さて、話を移して VSCode の DevContainer で Ruby 開発をする場合、インストールした gem を永続化しつつコンテナにマウントするには以下のようにワークスペース内の vendor
ディレクトリ等に突っ込んでおきたいところです。2
$ bundle config set --local path vendor/bundle
この設定により path 設定が記述された設定ファイルが通常は .bundle/config
として保存されるはずです。ローカル設定にしたことでワークスペース内に設定ファイルが作られ、コンテナを破棄しても再設定は不要となります。
しかし、前述のように Docker 公式の ruby イメージでは BUNDLE_APP_CONFIG
が設定されてしまっているため、このローカル設定は /usr/local/bundle/config
に保存されてしまいます。
bundle config
で確認してみると以下のような感じです。
$ bundle config
Settings are listed in order of priority. The top value will be used.
path
Set for your local app (/usr/local/bundle/config): "vendor/bundle"
app_config
Set via BUNDLE_APP_CONFIG: "/usr/local/bundle"
silence_root_warning
Set via BUNDLE_SILENCE_ROOT_WARNING: true
これでは Rebuild などによりコンテナを破棄した際に設定が失われてしまうため、都度 bundle config ...
を実行するか、/usr/local/bundle
をホストからマウントするようにするなどの工夫が必要になってしまいます。
スマートに解決するには BUNDLE_APP_CONFIG
自体を何とかした方が良いでしょう。
ちなみに公式イメージで BUNDLE_APP_CONFIG
が設定されているのは上記の逆の考え方で、「ホスト側の .bundle/config
がコンテナ内に影響を与えないようにするため」のようです3。
2020/09/30 追記
コンテナ内でしか作業しない (= ホストと .bundle/
や vendor/
を共有する必要がない) のであれば BUNDLE_APP_CONFIG
はそのままにして別途作成した volume を /usr/local/bundle
にマウントした方が良さそうです。特に Docker for Mac では bind mount 時の Disk I/O が遅いため、ワークスペース内に gem を入れる設定だと bundle install
等に時間がかかってしまう可能性があります。
対策
× BUNDLE_APP_CONFIG
に空文字を設定する
公式ドキュメント に記載されている方法です。
The environment variables we set are canonically listed in the above-linked Dockerfiles, but some of them include GEM_HOME, BUNDLE_PATH, BUNDLE_BIN, BUNDLE_SILENCE_ROOT_WARNING, and BUNDLE_APP_CONFIG.
If these cause issues for your use case (running multiple Ruby applications in a single container, for example), setting them to the empty string should be sufficient for undoing their behavior.
結論から言うとこの方法ではうまくいきません。
bundler は BUNDLE_APP_CONFIG
の有無だけを見ている4ので、空文字が設定されている場合はカレントディレクトリ直下に config
ファイルが作成されます。
実験してみましょう。
DevCotainer 用の Dockerfile で BUNDLE_APP_CONFIG
に空文字をセットします。
FROM ruby:2.7
# 空文字を設定
ENV BUNDLE_APP_CONFIG=
# 以下略
DevContaier をビルドして中に入ってみます。
ターミナル上ではなぜか BUNDLE_APP_CONFIG
が消えて問題なく動作します (理由は不明ですが VSCode の実装の問題かもしれません)。
$ bundle config
Settings are listed in order of priority. The top value will be used.
silence_root_warning
Set via BUNDLE_SILENCE_ROOT_WARNING: true
$ bundle config set --local path vendor/bundle
$ bundle config
Settings are listed in order of priority. The top value will be used.
path
Set for your local app (/workspace/sample_app/.bundle/config): "vendor/bundle"
silence_root_warning
Set via BUNDLE_SILENCE_ROOT_WARNING: true
問題となるのはデバッグ時です。
デバッガ (rdebug-ide
) を Gemfile で管理し、bundle install
でインストールしているケースを想定します。
以下のような launch.json
になるはずです。
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Local File",
"type": "Ruby",
"request": "launch",
"program": "${workspaceFolder}/main.rb",
"useBundler": true
}
]
}
デバッグを実行すると DEBUG CONSOLE に以下のように必要な gem が見つからない旨のエラーが出てしまいます。
bundler: failed to load command: rdebug-ide (/usr/local/bundle/bin/rdebug-ide)
Bundler::GemNotFound: Could not find concurrent-ruby-1.1.7 in any of the sources
/usr/local/lib/ruby/2.7.0/bundler/spec_set.rb:86:in `block in materialize'
/usr/local/lib/ruby/2.7.0/bundler/spec_set.rb:80:in `map!'
/usr/local/lib/ruby/2.7.0/bundler/spec_set.rb:80:in `materialize'
/usr/local/lib/ruby/2.7.0/bundler/definition.rb:170:in `specs'
/usr/local/lib/ruby/2.7.0/bundler/definition.rb:237:in `specs_for'
/usr/local/lib/ruby/2.7.0/bundler/definition.rb:226:in `requested_specs'
/usr/local/lib/ruby/2.7.0/bundler/runtime.rb:101:in `block in definition_method'
/usr/local/lib/ruby/2.7.0/bundler/runtime.rb:20:in `setup'
/usr/local/lib/ruby/2.7.0/bundler.rb:149:in `setup'
/usr/local/lib/ruby/2.7.0/bundler/setup.rb:20:in `block in <top (required)>'
/usr/local/lib/ruby/2.7.0/bundler/ui/shell.rb:136:in `with_level'
/usr/local/lib/ruby/2.7.0/bundler/ui/shell.rb:88:in `silence'
/usr/local/lib/ruby/2.7.0/bundler/setup.rb:20:in `<top (required)>'
これは、デバッグ用のサブプロセスでは BUNDLE_APP_CONFIG=""
が効いているために .bundle/config
が参照されず path
設定が読み込まれないためです。
△ BUNDLE_APP_CONFIG
に .bundle
のパスを設定してしまう (Dockerfile)
例えば以下のように Dockerfile などでローカル設定用の .bundle
の絶対パスを指定してしまう方法です。
ENV BUNDLE_APP_CONFIG=/workspace/.bundle
この方法であれば大体うまくいきますが、モノレポに複数のプロジェクトを突っ込んでいるケース (Gemfile
が複数あるケース) には対応できません。
△ BUNDLE_APP_CONFIG
に .bundle
のパスを設定してしまう (VSCode 設定)
例えば上記のデバッグがうまくいかない問題は以下のように解決できます。
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Local File",
"type": "Ruby",
"request": "launch",
"program": "${workspaceFolder}/main.rb",
"useBundler": true,
"env": {
"BUNDLE_APP_CONFIG": "${workspaceFolder}/.bundle"
}
}
]
}
この方法であれば Multi-root Workspace にすることでモノレポのケースにも対応することができます。
ただしこの設定だけではデバッグ用のサブプロセスにしか有効にならないので、必要に応じて settings.json
のターミナル設定や tasks.json
にも同様の設定を記述する必要があります。
また、何らかの理由で BUNDLE_APP_CONFIG
を設定して使っている人と launch.json
等の設定を共有することが難しくなります。
〇 devcontainer.json の設定で BUNDLE_APP_CONFIG
を削除する
下記のような設定を devcontainer.json
に追加することで、環境変数を空にするのでも上書きするのでもなく削除 (unset) することができます。
{
// ...
"remoteEnv": {
"BUNDLE_APP_CONFIG": null
},
// ...
}
この方法であればコンテナ内の VSCode プロセス及びそのサブプロセス全てで BUNDLE_APP_CONFIG
が削除されます。
個人的にはこの方法が最も副作用が少なそうなのでおすすめです。