For

2023.3.3

環境変数、process.env、.envファイルでできること

そもそも環境変数とは

環境変数(かんきょうへんすう、英語: environment variable)はオペレーティングシステム (OS) が提供するデータ共有機能の一つ。OS上で動作するタスク(プロセス)がデータを共有するための仕組みである。

wikipedia より

ということなので、OS上で動作するプロセスが参照できるグローバル変数ということになります。
私のOSはmacOSですが、まずは環境変数になにが定義されているのかterminalでコマンドを実行して確認してみます。

bash_____terminal_____$ ENV

MallocNanoZone=0
USER=kobayashi
COMMAND_MODE=unix2003
... // 40行くらい


定義された環境変数の一覧が表示されました。
これらはOS上で動作するタスクから参照可能な変数ということになります。

process.envとは

以降は実行環境はNodeでJavaScriptのアプリケーションとして話を進めていきます。
process.envとは上記の環境変数がバインドされたオブジェクトで、実行環境がNodeであればprocess.envに環境変数がバインドされています。
一番シンプルな例で確認してみます。

JavaScript_____index.js_____console.log(process.env);


上記のファイルを作成し、次のコマンドを実行します。

bash_____terminal_____$ node index.js


すると、最初にterminalで ENV コマンドを実行したときと同じ結果が得られます。
これで実行環境がNodeであればprocess.envから環境変数が参照できることがわかりました。

.envとは

意外と何気なく使っている人が多い .env ですが .env で定義した環境変数がなぜprocess.envから参照できるのか知っているでしょうか。
昨今のモダンな技術スタック(React.js、Next.jsなど)を使っていると .env というファイル名でプロジェクトのルートディレクトリに配置したら勝手にprocess.envから参照できるようになるんだ!と思っているかもしれませんが、そうではありません。
試しに先ほど index.js を作ったディレクトリで .env というファイルを以下のように作ってみます。

text_____.env_____MY_VALUE=my_value


そして index.js を次のように編集します。

JavaScript_____index.js_____console.log(process.env.MY_VALUE);


これで再度以下のコマンドを実行すると undefined となります。

bash_____terminal_____$ node index.js

undefined


.envはdotenvというライブラリでバインドされる

.env はなにか手を加えないとprocess.envにバインドされないことがわかりましたが、これをバインドしてくれるのが dotenv というライブラリです。
やっていることはシンプルで main.js も100行ちょっとしかないライブラリなので良かったら読んでみるとより理解が深まるかと思います。
早速試すために、以下のようにコマンドを実行します。

 bash_____terminal_____$ npm init -y
$ npm i dotenv


次に index.js を以下のように編集します。

JavaScript_____index.js_____console.log('MY_VALUE:', process.env.MY_VALUE);

const dotenv = require('dotenv').config();
console.log('\n= = = dotenv is loaded. = = =\n');

console.log('MY_VALUE:', process.env.MY_VALUE);


そして以下のコマンドを実行します。

bash_____terminal_____$ node index.js

MY_VALUE: undefined

= = = dotenv is loaded. = = =

MY_VALUE: my_value


上記より、dotenvを利用して .env で定義した MY_VALUE という環境変数がprocess.envにバインドされたことがわかりました。
npx create-next-app でNext.jsアプリケーションを作成すると、node_modules配下にdotenvというディレクトリが見つかりますが、モダンな技術スタックなプロジェクトならまず見つかるはずです。

.envで環境変数を参照する環境変数を定義する

次のような環境変数定義を見たことがないでしょうか。

text_____.env_____MY_VALUE=my_value
MY_MESSAGE="MY_VALUE is ${MY_VALUE}"


いかにも MY_MESSAGE の中で MY_VALUE が展開されそうです。
上記のように .env を更新したあと、次のように index.js を更新します。

JavaScript_____index.js_____console.log('MY_VALUE:', process.env.MY_VALUE);
console.log('MY_MESSAGE:', process.env.MY_MESSAGE);

const dotenv = require('dotenv').config();
console.log('\n= = = dotenv is loaded. = = =\n');

console.log('MY_VALUE:', process.env.MY_VALUE);
console.log('MY_MESSAGE:', process.env.MY_MESSAGE);


これで実行してみます。

bash_____terminal_____$ node index.js

MY_VALUE: undefined
MY_MESSAGE: undefined

= = = dotenv is loaded. = = =

MY_VALUE: my_value
MY_MESSAGE: MY_VALUE is ${MY_VALUE}


MY_VALUE は展開されずにそのままテキストとして参照されました。
これが展開されるようにしてくれているのがdotenv-expandというライブラリです。
dotenv-expandをインストールして確認します。

bash_____terminal_____$ npm i dotenv-expand


次に index.js を以下のように更新します。

JavaScript_____index.js_____console.log('MY_VALUE:', process.env.MY_VALUE);
console.log('MY_MESSAGE:', process.env.MY_MESSAGE);

const dotenv = require('dotenv').config();
console.log('\n= = = dotenv is loaded. = = =\n');

console.log('MY_VALUE:', process.env.MY_VALUE);
console.log('MY_MESSAGE:', process.env.MY_MESSAGE);

require('dotenv-expand').expand(dotenv);
console.log('\n= = = dotenv-expand is loaded. = = =\n');

console.log('MY_VALUE:', process.env.MY_VALUE);
console.log('MY_MESSAGE:', process.env.MY_MESSAGE);


これで再度実行してみます。

bash_____terminal_____$ node index.js

MY_VALUE: undefined
MY_MESSAGE: undefined

= = = dotenv is loaded. = = =

MY_VALUE: my_value
MY_MESSAGE: MY_VALUE is ${MY_VALUE}

= = = dotenv-expand is loaded. = = =

MY_VALUE: my_value
MY_MESSAGE: MY_VALUE is my_value


dotenv-expandで MY_MESSAGE の中の ${MY_VALUE}MY_VALUE が参照されて MY_MESSAGE の中で展開されました。

まとめ

モダンな開発環境で当然のように .env を利用していたのにこのあたりを知らなかったという人の役に立てば幸いです。
このあたりがわかっていると、例えばAWS Secrets Managerで定義した値をECSのタスクの環境変数として参照できるように定義し、コンテナ内のアプリケーションからAWS Secrets Managerの値を参照するようなことができたりするので、基本を抑えておくと役に立つのではないかなと思います。