Rails で独自環境変数に APP_ENV を使ってはいけない
TL;DR
- puma.rb の最新テンプレートでは
environmentをRAILS_ENVから読み込む設定が冗長なものとして削除された - Puma では
RAILS_ENVよりもAPP_ENVを優先して読み込む - Puma が読み込んだ環境名は
RACK_ENVとして設定される -
RAILS_ENVを指定せずAPP_ENVのみ指定した状態で Rails サーバーを起動すると、Rails.envがAPP_ENVの値になり意図しない挙動をした
ここから本文
リーナー開発チームの黒曜(@kokuyouwind)です。
リーナーでは Rails を使って Web アプリケーションを開発しており、なるべく最新の Rails バージョンや設定テンプレートに追従しています。
今日はそんな中で踏んだ RAILS_ENV と APP_ENV の挙動についてまとめます。
APP_ENV を独自の環境切り替えに使っていた
Rails において、 RAILS_ENV は基本的に development, test, production のいずれかの値に設定します。
独自設定でこれ以外の値を足すこともできますが、一般にはあまり推奨されないとされています。以下の neko314 さんの記事がわかりやすいです。
一方で、リーナーでは本番環境の他に検証用の環境としてステージング環境などがあり、これらの設定を切り替える必要があります。
そこで RAILS_ENV = development は開発者のローカル開発時の設定に寄せ、クラウドにデプロイされる本番環境・ステージング環境・開発環境はいずれも RAILS_ENV = production にしたうえで、独自の APP_ENV を leaner_dev, leaner_stg, leaner_prd とすることで読み込む設定を切り替えるようにしていました。[1]
実装としてはConfig gem で読み込むパスを APP_ENV に応じて追加する形にしています。
# APP_ENVに応じたConfigを読み込む
Settings.add_source!(Rails.root.join("config/settings/app/#{ENV.fetch('APP_ENV')}.yml").to_s)
Settings.reload!
例えば RAILS_ENV = production かつ APP_ENV = leaner_stg であれば、 config/settings/production.rb に加えて config/settings/app/leaner_stg.yml を読み込みます。
APP_ENV を指定したらローカル環境が起動しなくなった
あるときローカル環境でも少し設定を変えた状態で起動したくなり、 APP_ENV に local.fork を指定したところ、なぜか rails server が失敗するようになってしまいました。
ログを見ると、 puma が environment: local で起動しようとしている…?
Puma starting in single mode...
* Environment: local.fork
なんで…? と思い原因を調べてみたところ、 Puma の設定が原因でした。
puma.rb の environment 指定が消えた
以前の Rails が生成する puma.rb では、 environment に RAILS_ENV の値を設定し、存在しない場合は development をデフォルトにする設定が入っていました。
が、これが冗長だとして削除するコミットが Rails v8.1.1 に含まれています。
@@ -27,19 +27,6 @@
- # Specifies the `environment` that Puma will run in.
- rails_env = ENV.fetch("RAILS_ENV", "development")
- environment rails_env
リーナーでも、このテンプレート変更に追従し puma.rb から environment の指定を削除していました。
Puma では RAILS_ENV よりも APP_ENV を優先する
では、 environment の指定がない場合に RAILS_ENV を読み込む処理はどうなっているのでしょう。
lib/puma/configuration.rb を見てみましょう。
あれ、 RAILS_ENV 以外に APP_ENV と RACK_ENV も見ている…? なんなら APP_ENV の優先度が一番高い…?
これだと本番でも RAILS_ENV より APP_ENV が優先されてヤバいのでは…?
Rails では RAILS_ENV を優先する
Puma の environment の値がどう使われるのか、もう少し追ってみましょう。
この値は Puma::Launcher.set_rack_environment で RACK_ENV にも設定されます。
そうなると Rails.env にも影響するかも、と思ったのですが、こちらは RAILS_ENV を優先して読み込むため RAILS_ENV が設定されていれば問題なさそうです。
ほな大丈夫かー。
問題が発生するケース
はい、大丈夫じゃないですね。
まずコードで見て分かる通り、 RAILS_ENV が設定されておらず APP_ENV のみが設定されているケースでは APP_ENV の値が Rails.env に設定されてしまいます。
特に、ローカル環境では起動コマンドに応じて development と test が切り替わってほしいため、 RAILS_ENV を設定しないほうが便利です。この環境で APP_ENV を設定すると意図せず Rails.env にも設定されてしまう問題があります。
冒頭の「APP_ENV を指定したらローカル環境が起動しなくなった」というのは、このケースを踏んでしまったわけですね。
学び
-
APP_ENV環境変数を独自目的に使っている場合はpuma.rbからenvironment指定を消してはいけない - そもそも
APP_ENVのように一般的な名前の環境変数を独自目的に使ってはいけない
今回の問題は puma.rb の変更に起因するものでしたが、そもそも APP_ENV は Sinatra などでも環境指定に使われており、独自目的に使うのは避けるべきでした。
これに限らず、アプリケーション独自の環境変数は他と被らないよう prefix した命名規約にするのが良いですね。
リーナーでも取り急ぎは puma.rb の environment 指定を戻して対応しつつ、環境変数名を LEANER_APP_ENV のように独自であることがわかりやすいものに変える予定です。
あまりないケースだと思いますが、もし APP_ENV など一般的な名前の変数を独自目的に使っている方がいたら他山の石にしていただけると嬉しいです。
宣伝
こういうしょーもない失敗の話など、気軽に雑談しましょう!
「今年こんなことを頑張ったよ!」というドヤ LT 会をやるので東京や名古屋近郊の方はぜひお越しくださいー。
-
RAILS_ENV=production, APP_ENV=stagingだと本番かステージか紛らわしいので、APP_ENVの命名規約をRAILS_ENVと変えています。 ↩︎
Discussion