環境変数の渡し方にはどんな種類があるのかというお話

# はじめに

どうも、 @rabeneko と申します。
私は、N予備校( https://www.nnn.ed.nico/ )という学習サイトでプログラミング講師をしております。

N予備校では毎週火曜日と金曜日にプログラミング授業をしておりまして、そこで出た質問の解説をしております。
今回は、その授業中に出た、質問に対する回答をQiitaに書きたいと思います。

プログラミングを学んでいる方・学んでみたい方は、ぜひN予備校の授業を覗いてみてください。

https://www.nnn.ed.nico/pages/programming/

# 環境変数の話

今回は「環境変数」についてです。

Webサービスを実際にユーザさんに使ってもらう **「本番環境」** や、新機能の開発やバグの修正を行う **「開発環境」** など、プログラムが動く場所には「環境」というものがあります。

各環境では同じプログラムが動いているのですが、環境によって動きを変えたい時に渡す変数の事を **「環境変数」** と言います。

例えばNodeJSで動かすプログラムで、 以下のようなindex.jsを用意したとします。

```js:index.js
if (process.env.NODE_ENV === 'production') {
    console.log('本番環境です');
} else {
    console.log('開発環境です');
}
```

そして、環境変数である `NODE_ENV` に、動かす場所が本番環境だったら `production` を入れて実行することで、環境により動作を変えることができます。

```sh
$ node index.js
開発環境です
$ NODE_ENV=production node index.js 
本番環境です
```

# 環境変数の設定の仕方

今回の記事では、様々な環境変数の設定方法を解説していきます。
**このやり方じゃないとダメというのはありません** ので、メリット・デメリットに応じて、一番だと思ったやり方を選ぶのが良いと思います。

N予備校ではNodeJSをベースとした教材となっていますので、NodeJSで説明していきます。

## 検証するプログラム

例えば自己紹介をするだけの簡単なサービスがあったとします。

```js:self_introduction.js
const name = process.env.NAME ?? 'テスト';
const twitter_id = process.env.TWITTER_ID ?? 'test';
const twitter_url = process.env.TWITTER_URL ?? 'http://example.com/test';
console.log(`こんにちは。 ${name} です。 TwitterのIDは ${twitter_id} です。URLは ${twitter_url} です`);
```

まずは何も設定せずに実行すると、テストさんの情報が出力されます。

```
$ node self_introduction.js 
こんにちは。 テスト です。 TwitterのIDは test です。URLは http://example.com/test です
```
`process.env.NAME` の `NAME` の部分が環境変数です。
本番環境では、自分の情報に変更します。

`NAME` `TWITTER_ID` `TWITTER_URL` の環境変数を渡すということを考えていきます。

## コマンドラインで渡すやり方

まずは先ほど `NODE_ENV` で設定したような、コマンドラインで渡すやり方です。



```sh
$ NAME=らべねこ TWITTER_ID=@rabeneko TWITTER_URL=https://twitter.com/rabeneko node self_introduction.js
こんにちは。 らべねこ です。 TwitterのIDは @rabeneko です。URLは https://twitter.com/rabeneko です
```

このような形で、スペース区切りで複数の変数を渡すことができます。

誰でも気軽に環境変数を使うことができるメリットはありますが、変数が多かったり長かったりすると、管理が大変ですね。
ちょっとした環境変数の設定であればこれでも良さそうです。

## サーバ(パソコン)の環境変数を使う方法

使っているサーバ(パソコン)には、システム環境変数というものがあります。

試しにコマンドで `export -p` と実行すると、一覧を見ることができます

```sh
$ export -p
declare -x HOME="/root"
declare -x HOSTNAME="4c2e307a69f2"
declare -x LANG="ja_JP.UTF-8"
declare -x NODE_VERSION="14.15.4"
declare -x OLDPWD
declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
declare -x PWD="/app"
declare -x SHLVL="1"
declare -x TERM="xterm"
declare -x TZ="Asia/Tokyo"
declare -x YARN_VERSION="1.22.5"
```

これらのシステム環境変数は、プログラム中から呼び出すことができます。

```js:node_version.js
console.log(process.env.NODE_VERSION);
```

```sh
$ node node_version.js 
14.15.4
```

このシステム環境変数に仲間入りさせてもらうことで、環境変数を設定することが可能です。
exportコマンドを使って、以下のように実行します。

```sh
$ export NAME=らべねこ
$ export TWITTER_ID=@rabeneko
$ export TWITTER_URL=https://twitter.com/rabeneko
$ node self_introduction.js
こんにちは。 らべねこ です。 TwitterのIDは @rabeneko です。URLは https://twitter.com/rabeneko です
```

JavaScriptの実行時に長々と環境変数を書かなくて良くなり、管理しやすくなりましたね。
ところがこのやり方、一つ問題がありまして、一度サーバを閉じてしまうと設定が消えてしまいます。

```sh
$ node self_introduction.js
こんにちは。 テスト です。 TwitterのIDは test です。URLは http://example.com/test です
```

それを解決するのが `.bash_profile` です。
`.bash_profile` にコマンドを書いておくと、サーバの起動時にコマンドが実行されます。
`.bash_profile` はコマンドを実行するユーザのホーム下( `~`　) に作成します。
したがって、場所は `~/.bash_profile` となります。

**dockerの場合は `~/.bashrc` です**

```sh
$ vi ~/.bash_profile
```

で `.bash_profile` を開き、以下のように書いて保存します。

```sh
export NAME=らべねこ
export TWITTER_ID=@rabeneko
export TWITTER_URL=https://twitter.com/rabeneko
```

これで次回のサーバ起動時から、これらの環境変数が設定された状態でプログラムを実行する事ができます。

でもこの後サーバを再起動するのは大変です。そこで、 `~/.bash_profile` を実行するコマンドがあります。
セットで覚えておくのが良いかと思います。

```sh
source ~/.bash_profile
```

これで実行すると、環境変数が読み込まれた状態で実行されます。

```
$ node self_introduction.js
こんにちは。 らべねこ です。 TwitterのIDは @rabeneko です。URLは https://twitter.com/rabeneko です
```

これで環境変数は `~/.bash_profile` で管理され、実行するときも環境変数を考えなくて良くなりました。

しかし1点だけデメリットがあります。

それは、もしほかのサーバで別のサービスが動いていた場合、そのサービスとも環境変数が共有されるということです。
そのため、ありきたりな名前をつけると、他のサービスに予期せぬ影響を与えることがあります。


`NAME`　などのありきたりな名前は他のサービスの環境変数としても使いそうですよね。
その場合はどちらかが、別の変数名をつけなくてはいけなくなります。

基本的には、「サービス名を先頭につける」ことで変数被りを回避することが多いです。
例えばWebサービス名が「自己紹介」(self_introduction)だとしたら、 `~/.bash_profile` は

```sh
export SELF_INTRODUTION_NAME=らべねこ
export SELF_INTRODUTION_TWITTER_ID=@rabeneko
export SELF_INTRODUTION_TWITTER_URL=https://twitter.com/rabeneko
```

と、**「SELF_INTRODUTION_」** を先頭につけて、これは「自己紹介サービスの環境変数ですよ〜」ということを表します。

もちろん `self_introduction.js` も修正します。

```js:self_introduction.js
const name = process.env.SELF_INTRODUTION_NAME ?? 'テスト';
const twitter_id = process.env.SELF_INTRODUTION_TWITTER_ID ?? 'test';
const twitter_url = process.env.SELF_INTRODUTION_TWITTER_URL ?? 'http://example.com/test';
console.log(`こんにちは。 ${name} です。 TwitterのIDは ${twitter_id} です。URLは ${twitter_url} です`);
```

この `.bash_profile` を用いた渡し方は一般的でして、このやり方を採用しているサービスも多いです。

NodeJSに限らず、どのサービスでも使えるやり方であるのが強みです。

# `.env`を使ったやり方

複数のサービスを運営していますと、この `.bash_profile` がいろんなサービスの環境変数でごっちゃごちゃになってきます。

```sh
export SELF_INTRODUTION_NAME=らべねこ
export SELF_INTRODUTION_TWITTER_ID=@rabeneko
export SELF_INTRODUTION_TWITTER_URL=https://twitter.com/rabeneko
export WAKUWAKU_CHAT_NAME=ワクワクチャット
export WAKUWAKU_CHAT_DATABASE_URL=postgresql://USERNAME:PASSWORD@DATABASE_HOST/DATABASE_NAME
export ENJOY_DIARY_NAME=楽しい日記サービス
export ENJOY_DIARY_SERVICE_ID=enjoy_diary
export ENJOY_DIARY_SERVICE_PASSWORD=enjoyjoy2022
```

※ イメージです

環境変数は `~/.bash_profile` ではなく、自分のプロジェクトフォルダ内で管理するほうが良いという発想から生まれたのが `.env` です。
NodeJSでは、 `dotenv` というライブラリで実現します。

```sh
$ yarn add dotenv
```

で、dotenvライブラリをインストールします。

その後、プロジェクトフォルダの直下に `.env` ファイルを作成し、

```sh:.env
NAME=らべねこ
TWITTER_ID=@rabeneko
TWITTER_URL=https://twitter.com/rabeneko
```

と書いて保存します。

そして、self_introduction.jsもdotenvを使う旨の行を追加します。


```js:self_introduction.js

// この行を追加
require('dotenv').config();

const name = process.env.NAME ?? 'テスト';
const twitter_id = process.env.TWITTER_ID ?? 'test';
const twitter_url = process.env.TWITTER_URL ?? 'http://example.com/test';
console.log(`こんにちは。 ${name} です。 TwitterのIDは ${twitter_id} です。URLは ${twitter_url} です`);
```

これで実行すると

```
$ node self_introduction.js
こんにちは。 らべねこ です。 TwitterのIDは @rabeneko です。URLは https://twitter.com/rabeneko です
```

となります。
NodeJS以外の言語だとやり方が違うとはいえ、だいたいの言語にこういった `.env` の考え方がありますので、他の言語でもやり方を調べてみると何かしら載ってます。

しかしプロジェクトフォルダにあると、Gitにコミットしたくなっちゃいますよね。
基本的には `.env` はGitにコミットしません。
といいますのも、開発中は `.env` は開発環境の情報なので、本番環境の環境変数を書いてGitにコミットすると、 **開発の度に手元のパソコンで `.env` を開発環境の情報に書き換えないといけない** んですね。
これは超だるいです。

これを回避するには、dotenvの「 `.env` ファイルの場所を自由に設定できる機能」を使います。
`.env.development` `.env.production` のように、開発環境と本番環境でファイルを分けて使い分けるんですね。

`self_introduction.js` も以下のように書き換えます。

```js:self_introduction.js

if (process.env.NODE_ENV === 'production') {
  require('dotenv').config({ path: '.env.production' });
} else {
  require('dotenv').config({ path: '.env.development' });
}

const name = process.env.NAME ?? 'テスト';
const twitter_id = process.env.TWITTER_ID ?? 'test';
const twitter_url = process.env.TWITTER_URL ?? 'http://example.com/test';
console.log(`こんにちは。 ${name} です。 TwitterのIDは ${twitter_id} です。URLは ${twitter_url} です`);
```

こうすれば、開発環境、本番環境それぞれの.envを、別々に管理することができます。

この場合は、本番環境のサーバ上では `NODE_ENV=production` をつけて実行することになります。

```sh
$ NODE_ENV=production node self_introduction.js
```

また、本番環境の環境変数を公開状態のGithubリポジトリにプッシュしてしまうと、もしパスワードなどが書かれていた場合にセキュリティ的にも大問題になることがあります。（こちらのほうが問題としては大きい）
Githubで管理するのは楽ですが、楽であるがゆえに、誰でも簡単に秘密の情報にアクセスできてしまいますね。

![kojinjouhou_rouei_businessman.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1972052/7b8c7173-a1e6-1da1-cec0-50462975dbfe.png)


これまでの、「コマンドラインで設定するやり方」や「システムの環境変数」にその問題は無いのかと言われると、全く無いわけではないですが、そもそもこの2つのやり方は、**「サーバにログインが必要」という前提がある** ので、サーバに入られない限り安全と言うこともできるんですね。

なので、 `.env` をGit管理しないということは、サーバにログインして自分で `.env` ファイルを作って編集する必要があるんですね。

それに、編集後は `.env` ファイルを再読み込みするために、サーバを一瞬止めないといけません。
なので、ユーザに一瞬使えない時間というものが発生してしまいます。

このように、 `.dotenv` も色々な問題もあるので、私は個人サイトをいくつか持ってますが、実際のところは `.env` は使わずに、結局全部 `~/.bash_profile` に書いちゃってます。

Git管理できない以上は `.env` を使う価値はそこまで大きくなくて、なら 「環境変数は全部 `~.bash_profile` に書く」 というルールにしたほうが分かりやすいという方針です。（これは私の方針で、絶対正しいというわけではないです）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1972052/d1c7a869-fe74-b180-31b6-d6506788df58.png)

Githubにはリポジトリを非公開にする機能（プライベートリポジトリ）があります。
なので、 **「もう絶対プライベートリポジトリ！コードは誰にも公開せずに墓場まで持っていく！」** と割り切ることができれば、プライベートリポジトリにして、 `.env.development` `.env.production` をコミットしちゃうのも、ありだと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1972052/e81d865b-e434-870e-acb3-ef2324bba864.png)

(墓場まで誰にも見られなければどうということはない)

# 墓場まで持っていかない場合に `.env` とGit管理を両立させるには？

ただ、もちろんチーム開発の場合はこれは通用しません。
チームメンバーの中に秘密の情報にアクセスさせてはいけないメンバーがいる場合には問題ですよね。
チームメンバー全員で墓場まで付き添ってもらうわけにもいかず…


なので、仕事の場合は`.env` とGit管理を両立させるにはそれなりの工夫が必要があります。

参考までに、仕事では環境変数を扱うだけのリポジトリを別で用意して、そちらから環境変数を読み込むようにしていました。

今回の自己紹介サービスであれば、 `self_introduction_secret` というプライベートリポジトリを用意して、その下に `.env` を用意するというイメージです。

で、以下のように、`self_introduction_secret` の直下の `.env` を読み込むようにプログラムも書き換えます。

```js:self_introduction.js

if (process.env.NODE_ENV === 'production') {
  require('dotenv').config({ path: '../self_introduction_secret/.env' });
}

const name = process.env.NAME ?? 'テスト';
const twitter_id = process.env.TWITTER_ID ?? 'test';
const twitter_url = process.env.TWITTER_URL ?? 'http://example.com/test';
console.log(`こんにちは。 ${name} です。 TwitterのIDは ${twitter_id} です。URLは ${twitter_url} です`);
```

こうすれば、 `.env` の管理（チームメンバーのうち、一部のメンバーしか見られない）とプログラムの管理（チームメンバーは全員見られる）を分けることができ、かつGit管理もできます。

他にも、Githubの環境変数＆Github Actionsを組み合わせるやり方もありますし、Herokuの場合はHerokuのサイトで環境変数を登録して管理するやり方もありますが、長くなるので記事のみの紹介で…

https://blog.shibayan.jp/entry/20201217/1608190413

https://qiita.com/mzmz__02/items/64db94b8fc67ee0a9068

環境変数の渡し方には色々な方法がありますので、メリット・デメリットに応じて、自分がやりやすいやり方で使ってみてください。

長文になりましたがお付き合い頂きありがとうございました。