dotenvで環境変数ファイルの読み込みを複数回おこなうと基本的には先勝ちになる

Node.jsで環境変数ファイルを読み込むdotenvを使って、複数回読み込みをおこなったときにどの環境変数ファイルの環境変数の値が有効になるのかを検証しました。

また、Node.js v20.6.0から追加された--env-fileオプションによる環境変数ファイルの読み込みも試しました。

はじめに

dotenvは、環境変数が保存されているファイル(デフォルトでは.env)を読み込んで変数process.envに格納してくれるモジュールです。 .envファイルを変更することで、ソースコードを変更することなく設定を変更することができます。

例えば、.envファイルに以下のように書き込みます。

PORT=3000

JavaScriptファイルでは以下のようにすることで、.envファイルの値を利用できるようになります。

require('dotenv').config();
// ESMなら以下のようにする
// import 'dotenv/config';

console.log(process.env.PORT);
// 3000

変数process.envは本来、Node.jsをCLIを使って呼び出したときの環境変数が格納されます。 例えば、index.jsを以下のようにします。

console.log(process.env.PORT);

このファイルを以下のように環境変数を指定して呼び出すとその値が格納されていることがわかります。

$ PORT=3000 node index.js
3000

また、Node.jsにはv20.6.0から追加された--env-fileオプションが存在し、こちらもdotenvと同じように指定されたファイルを読み込んで、変数process.envに格納します。 このオプションはStability 1とされており、まだ実験段階(Experimental)とされています。Stableにはなっていないため、ご注意ください。

.envファイル、index.jsはこれまでと同じにします。

CLI--env-fileオプションを使うことで.envファイルを読み込んで利用できるようになります。

$ node --env-file=.env index.js 
3000

検証

検証に使ったコードはGitHubにあります。

使った環境のバージョンは以下のとおりです。

  • Node.js v20.17.0
  • dotenv v16.4.5

また、使った環境変数ファイルは3つあり、先に紹介しておきます。

.envファイル

MY_ENV_VALUE=DOTENV_FILE
ALPHA=AAA

.env.nextファイル

MY_ENV_VALUE=DOTENV_NEXT_FILE
BETA=BBB

.env.next2ファイル

MY_ENV_VALUE=DOTENV_NEXT2_FILE
CHARLIE=CCC

dotenvで環境変数ファイルを複数回読み込むと先勝ち

以下のように.envファイル、.env.nextファイルの順で読み込みます。 (dotenvではpathというオプションによって読み込みをおこなうファイルパスを指定できます。)

2つの環境変数ファイルで重複しているキーは、先に読み込まれたファイルの値が優先されます。 以下の例だと、MY_ENV_VALUEが重複しており、先に読み込んだ.envファイルの値が使われています。

また、後の環境変数ファイルに存在しないキーも先に読み込まれたファイルの値が使われ(キーALPHA)、後のファイルにしか存在しないキーは後のファイルの値が使われます(キーBETA)。

require('dotenv').config();

console.log('index.js', 'after reading .env file MY_ENV_VALUE:', process.env.MY_ENV_VALUE);
console.log('index.js', 'after reading .env file ALPHA:', process.env.ALPHA);
console.log('index.js', 'after reading .env file BETA:', process.env.BETA);
// index.js after reading .env file MY_ENV_VALUE: DOTENV_FILE
// index.js after reading .env file ALPHA: AAA
// index.js after reading .env file BETA: undefined

require('dotenv').config({ path: '.env.next' });

console.log('===');
console.log('index.js', 'after reading .env.next file MY_ENV_VALUE:', process.env.MY_ENV_VALUE);
console.log('index.js', 'after reading .env.next file ALPHA:', process.env.ALPHA);
console.log('index.js', 'after reading .env.next file BETA:', process.env.BETA);
// index.js after reading .env.next file MY_ENV_VALUE: DOTENV_FILE
// index.js after reading .env.next file ALPHA: AAA
// index.js after reading .env.next file BETA: BBB

dotenvでoverrideオプションを使って後勝ちにする

後に読み込まれたファイルを優先するには、overrideオプションを使います。 overrideオプションにtrueを設定することで重複しているキーは後に読み込まれたファイルの値が優先されて使われます。

以下の例だとMY_ENV_VALUEが重複しており、後に読み込んだ.env.nextファイルの値が使われています。 先のファイルの読み込みでoverrideオプションを使っても、後のファイル読み込みでoverrideオプションが使われていれば後勝ちとなります。 また、重複していないキーの値は上書きされません。

require('dotenv').config({ override: true });

console.log('index.js', 'after reading .env file MY_ENV_VALUE:', process.env.MY_ENV_VALUE);
console.log('index.js', 'after reading .env file ALPHA:', process.env.ALPHA);
console.log('index.js', 'after reading .env file BETA:', process.env.BETA);
// index.js after reading .env file MY_ENV_VALUE: DOTENV_FILE
// index.js after reading .env file ALPHA: AAA
// index.js after reading .env file BETA: undefined

require('dotenv').config({ path: '.env.next', override: true });

console.log('===');
console.log('index.js', 'after reading .env.next file MY_ENV_VALUE:', process.env.MY_ENV_VALUE);
console.log('index.js', 'after reading .env.next file ALPHA:', process.env.ALPHA);
console.log('index.js', 'after reading .env.next file BETA:', process.env.BETA);
// index.js after reading .env.next file MY_ENV_VALUE: DOTENV_NEXT_FILE
// index.js after reading .env.next file ALPHA: AAA
// index.js after reading .env.next file BETA: BBB

さらに、overrideオプションを使いつつ、最初に読み込んだ環境変数ファイルを、別のファイルを読み込んだあとにもう一度読み込むと、一番最後に読み込んだファイルの値が使われます。

require('dotenv').config({ override: true });

console.log('index.js', 'after reading .env file MY_ENV_VALUE:', process.env.MY_ENV_VALUE);
console.log('index.js', 'after reading .env file ALPHA:', process.env.ALPHA);
console.log('index.js', 'after reading .env file BETA:', process.env.BETA);
// index.js after reading .env file MY_ENV_VALUE: DOTENV_FILE
// index.js after reading .env file ALPHA: AAA
// index.js after reading .env file BETA: undefined

require('dotenv').config({ path: '.env.next', override: true });

console.log('===');
console.log('index.js', 'after reading .env.next file MY_ENV_VALUE:', process.env.MY_ENV_VALUE);
console.log('index.js', 'after reading .env.next file ALPHA:', process.env.ALPHA);
console.log('index.js', 'after reading .env.next file BETA:', process.env.BETA);
// index.js after reading .env.next file MY_ENV_VALUE: DOTENV_NEXT_FILE
// index.js after reading .env.next file ALPHA: AAA
// index.js after reading .env.next file BETA: BBB

require('dotenv').config({ override: true });

console.log('===');
console.log('index.js', 'after reading .env file MY_ENV_VALUE:', process.env.MY_ENV_VALUE);
console.log('index.js', 'after reading .env file ALPHA:', process.env.ALPHA);
console.log('index.js', 'after reading .env file BETA:', process.env.BETA);
// index.js after reading .env file MY_ENV_VALUE: DOTENV_FILE
// index.js after reading .env file ALPHA: AAA
// index.js after reading .env file BETA: BBB

Node.js CLIで指定された環境変数はデフォルトでdotenvよりも優先されるが、overrideオプションによって上書きされる

cli.jsファイルを以下のように設定し、

console.log('index.js', 'before reading .env file MY_ENV_VALUE:', process.env.MY_ENV_VALUE);
// index.js before reading .env file MY_ENV_VALUE: CLI_VALUE

require('dotenv').config();

console.log('===');
console.log('index.js', 'after reading .env file MY_ENV_VALUE:', process.env.MY_ENV_VALUE);
// index.js after reading .env file MY_ENV_VALUE: CLI_VALUE

require('dotenv').config({ path: '.env.next', override: true });

console.log('===');
console.log('index.js', 'after reading .env.next file using override=true MY_ENV_VALUE:', process.env.MY_ENV_VALUE);
// index.js after reading .env.next file using override=true MY_ENV_VALUE: DOTENV_NEXT_FILE

環境変数を指定しながら、Node.js CLIでJSファイルを実行します。

$ MY_ENV_VALUE=CLI_VALUE node cli.js

dotenvで環境変数ファイルを読み込んでも重複しているキー(MY_ENV_VALUE)はCLIで指定した環境変数の値が勝ちます。 ただし、overrideオプションを使って環境変数ファイルを読み込むとdotenvで読み込んだ環境変数ファイルの値が優先となります。

--env-fileオプションは後勝ち

env.jsを以下のようにし、

console.log('index.js', 'after reading .env file MY_ENV_VALUE:', process.env.MY_ENV_VALUE);
console.log('index.js', 'after reading .env file ALPHA:', process.env.ALPHA);
console.log('index.js', 'after reading .env file BETA:', process.env.BETA);
// index.js after reading .env file MY_ENV_VALUE: DOTENV_NEXT_FILE
// index.js after reading .env file ALPHA: AAA
// index.js after reading .env file BETA: BBB

Node.js CLI--env-fileオプションを複数指定して呼び出します。

$ node --env-file=.env --env-file=.env.next env.js

環境変数ファイルで重複しているキー(MY_ENV_VALUE)は後に指定したファイルの値が利用され、重複していないキーはそれぞれの値が使われています。

--env-fileオプションで読み込んだ環境変数ファイルはデフォルトでdotenvよりも優先されるが、overrideオプションによって上書きされる

env2.jsファイルを以下のようにし、

console.log('index.js', 'after reading .env file MY_ENV_VALUE:', process.env.MY_ENV_VALUE);
console.log('index.js', 'after reading .env file ALPHA:', process.env.ALPHA);
console.log('index.js', 'after reading .env file BETA:', process.env.BETA);
// index.js after reading .env file MY_ENV_VALUE: DOTENV_FILE
// index.js after reading .env file ALPHA: AAA
// index.js after reading .env file BETA: undefined

require('dotenv').config({ path: '.env.next'});
console.log('===');
console.log('index.js', 'after reading .env.next file MY_ENV_VALUE:', process.env.MY_ENV_VALUE);
console.log('index.js', 'after reading .env.next file ALPHA:', process.env.ALPHA);
console.log('index.js', 'after reading .env.next file BETA:', process.env.BETA);
// index.js after reading .env.next file MY_ENV_VALUE: DOTENV_FILE
// index.js after reading .env.next file ALPHA: AAA
// index.js after reading .env.next file BETA: BBB

require('dotenv').config({ path: '.env.next2', override: true});
console.log('===');
console.log('index.js', 'after reading .env.next2 file with override MY_ENV_VALUE:', process.env.MY_ENV_VALUE);
console.log('index.js', 'after reading .env.next2 file with override ALPHA:', process.env.ALPHA);
console.log('index.js', 'after reading .env.next2 file with override BETA:', process.env.BETA);
// index.js after reading .env.next file with override MY_ENV_VALUE: DOTENV_NEXT2_FILE
// index.js after reading .env.next file with override ALPHA: AAA
// index.js after reading .env.next file with override BETA: BBB

呼び出します。

$ node --env-file=.env env2.js

dotenvで環境変数ファイルを読み込んでも重複しているキー(MY_ENV_VALUE)は--env-fileオプションで読み込んだ環境変数の値が勝ちます。 ただし、overrideオプションを使って環境変数ファイルを読み込むとdotenvで読み込んだ環境変数ファイルの値が優先となります。

Node.js CLI環境変数を指定したときと同じく、dotenvはデフォルトでは負けです。

CLIで指定した環境変数 > --env-fileオプション > dotenv、ただしoverrideオプションによってdotenvが勝つ

これまでの指定方法3つを混ぜて使います。

env3.js

console.log('index.js', 'after reading .env file MY_ENV_VALUE:', process.env.MY_ENV_VALUE);
console.log('index.js', 'after reading .env file ALPHA:', process.env.ALPHA);
console.log('index.js', 'after reading .env file BETA:', process.env.BETA);
// index.js after reading .env file MY_ENV_VALUE: DOTENV_FILE
// index.js after reading .env file ALPHA: CLI_VALUE
// index.js after reading .env file BETA: undefined

require('dotenv').config({ path: '.env.next'});
console.log('===');
console.log('index.js', 'after reading .env.next file MY_ENV_VALUE:', process.env.MY_ENV_VALUE);
console.log('index.js', 'after reading .env.next file ALPHA:', process.env.ALPHA);
console.log('index.js', 'after reading .env.next file BETA:', process.env.BETA);
// index.js after reading .env.next file MY_ENV_VALUE: DOTENV_FILE
// index.js after reading .env.next file ALPHA: CLI_VALUE
// index.js after reading .env.next file BETA: BBB

require('dotenv').config({ path: '.env.next2', override: true});
console.log('===');
console.log('index.js', 'after reading .env.next2 file with override MY_ENV_VALUE:', process.env.MY_ENV_VALUE);
console.log('index.js', 'after reading .env.next2 file with override ALPHA:', process.env.ALPHA);
console.log('index.js', 'after reading .env.next2 file with override BETA:', process.env.BETA);
// index.js after reading .env.next2 file with override MY_ENV_VALUE: DOTENV_NEXT2_FILE
// index.js after reading .env.next2 file with override ALPHA: CLI_VALUE
// index.js after reading .env.next2 file with override BETA: BBB

require('dotenv').config({ path: '.env', override: true});
console.log('===');
console.log('index.js', 'after reading .env.next2 file with override MY_ENV_VALUE:', process.env.MY_ENV_VALUE);
console.log('index.js', 'after reading .env.next2 file with override ALPHA:', process.env.ALPHA);
console.log('index.js', 'after reading .env.next2 file with override BETA:', process.env.BETA);
// index.js after reading .env.next2 file with override MY_ENV_VALUE: DOTENV_FILE
// index.js after reading .env.next2 file with override ALPHA: AAA
// index.js after reading .env.next2 file with override BETA: BBB

呼び出し

$ ALPHA=CLI_VALUE node --env-file=.env env3.js

CLI環境変数として指定したキーALPHAの値は、--env-fileオプションで読み込まれた値よりも優先されます。 --env-fileオプションで読み込まれた値は、dotenvで読み込んだMY_ENV_VALUEの値よりも優先されます。

ただし、CLI環境変数として指定した値も--env-fileオプションで読み込まれた値もdotenvのoverrideオプションを使うことで上書きできます。

おわりに

結論として強い順に並べると、

overrideオプションを使ったdotenvで後のファイル > overrideオプションを使ったdotenvで先のファイル > > CLIでの環境変数 > --env-fileオプション > dotenvで先のファイル > dotenvで後のファイル

となります。

紹介したdotenv、--env-fileオプションと環境変数という3つを全部同時に使うことはないでしょうが、dotenvと環境変数--env-fileオプションと環境変数という組み合わせは発生するでしょう。 その際にお役に立ったら嬉しいです。

--env-fileオプションが将来的にStableとなったとき、dotenvモジュールの利用はなくなるのかもしれません。

参考