Docker コンテナに注入する環境変数、どれが優先される?適用のされ方を実際に調べてみた
最終的に Kubernetes 上で動かすつもりのアプリを作っていると、環境変数を注入してアプリに使用させることが多々ある。
Node.js 製のアプリでは、dotenv という npm パッケージで .env
ファイルを読み込んだりできるので、開発環境ではコレを使って環境変数を擬似的に設定したりもする。
- 参考 : dotenv - npm
そんなアプリを Docker イメージ化して、開発環境で動作確認したいな、という時に、どうやって環境変数を注入するか、手段がいくつか存在する。
今回はそれらを確認し、どのように Docker コンテナに環境変数を注入できるか、複数の手法を併用したらどの環境変数設定が優先されるのか、といったことをまとめていく。
目次
サンプルコード
今回は dotenv を使用した Node.js 製のアプリを想定し、簡単なサンプルコードを用意した。
package.json
dotenv
をインストールしてある
{
"name": "practice-env",
"private": true,
"scripts": {
"start": "node index.js"
},
"dependencies": {
"dotenv": "8.2.0"
}
}
index.js
ENV_1
・ENV_2
・ENV_3
という環境変数を3秒ごとに出力している- 自分で試す場合は、ココで用意する環境変数をいくつか増やしたりしてやると、バリエーション確認がしやすいだろう
const envFilePath = `${__dirname}/env-dir/.env`;
require('dotenv').config({ path: envFilePath });
setInterval(() => {
console.log(`Env : [${envFilePath}]
ENV_1 : [${process.env.ENV_1}]
ENV_2 : [${process.env.ENV_2}]
ENV_3 : [${process.env.ENV_3}]`);
}, 3000);
./env-dir/.env
dotenv
で読み込む環境変数ファイル- 今回は調査のため、プロジェクト直下ではなく、専用の階層を設けてファイルを用意している
- 中身はデタラメに用意しておけば良い。全ての環境変数に値を与えず、一部だけ定義してやっても良いだろう
ENV_1=This is from .env file 1
ENV_2=This is from .env file 2
ENV_3=This is from .env file 3
Dockerfile
- 今回は検証のため、Dockerfile の中で
ENV_1
のみ値を設定してやっている
- 今回は検証のため、Dockerfile の中で
FROM node:14-alpine3.12
WORKDIR /app/
# 環境変数を直接指定する
ENV ENV_1='From Dockerfile'
# dotenv で参照する環境変数ファイルをコピーする
COPY ./env-dir/.env /app/env-dir/
COPY ./package.json ./index.js /app/
RUN npm install
CMD ["npm", "start"]
ココまで用意できたら、
$ docker build -t practice-env:0 ./
というコマンドで Docker イメージをビルドしてやる。ココまでで環境変数に対して値を注入しているのは2ヶ所である。
.env
ファイルをdotenv
で読み込むENV
で環境変数を設定する
準備はココまで。
環境変数の注入方法を知る
こうして作成した Docker イメージを元に、色々な方法で環境変数を注入していく。
その際、同じ名前の環境変数に対して、異なる値を注入しようとした時に、どのやり方で渡した値が優先されるのか、検証する。
優先順位は不等号記号で表現する。例えば A < B
と書いたら、「A の方法で注入した値より、B の方法で注入した値の方が優先された」という表現になる。
1. そのまま実行 → .env
< ENV
まずはそのまま実行してみる。
$ docker run --rm practice-env:0
出力を確認すると、COPY
した .env
ファイルを dotenv
が読み込んでいることは確認できる。
しかし、環境変数 ENV_1
については、Dockerfile で ENV
命令にて設定した値の方が優先されていた。
dotenv
は、既に値が設定されている場合は .env
ファイルの内容を適用しないことが分かる。コレは実際のコードを見ても明らかである。
ということで、.env
ファイルとシステム環境変数とでは、常にシステム環境変数の方が優先され、.env
ファイルに書いても上書きはできないことを押さえておこう。
2. .env
ファイルをボリュームマウントで差替 → イメージ内のファイル < ボリュームマウントしたファイル
次は docker run
コマンド時に --volume
(-v
) オプションを使ってみる。
$ docker run --rm --volume="$(pwd)/docker-volume:/app/env-dir" practice-env:0
このようにして、Docker イメージに取り込み済みの .env
ファイルを、別の内容の .env
ファイルに差し替えてみる。コレをやるために、環境変数ファイル用のディレクトリ env-dir/
を設けていたワケ。
--volume
オプションは、Docker イメージ内に既に同名のファイルがあっても、ディレクトリごと差し替える形で中身を入れ替えてしまう。というワケで、Docker イメージ内に COPY
しておいた .env
ファイルの内容は全て破棄され、ボリュームマウントした .env
ファイルを dotenv
が解釈する形となる。
3. --env-file
オプション → dotenv
< ENV
命令 < --env-file
次は、dotenv
とよく似てはいるが、Docker 自身が指定のファイルをシステム環境変数として注入してくれる、--env-file
オプションを使ってみる。
$ docker run --rm --env-file='./docker-env-file/ENV-FILE' practice-env:0
--env-file
オプションは dotenv
などとは関係なく、システム環境変数として注入されるので、シェルの世界で $ env
コマンドを実行しても確認できるモノとなる。
このオプションを使うと、dotenv
で注入した値は勿論、Dockerfile
の ENV
命令で定義した環境変数であっても、上書き適用できる。
4. --env
オプション → dotenv
< ENV
命令 < --env
次は .env
ファイルを渡すのではなく、コマンドライン上で環境変数の Key・Value を渡してやる、--env
(-e
) オプションを使ってみる。
$ docker run --rm --env 'ENV_1=from env option`
コチラも --env-file
と同様、dotenv
で読み込む値、ENV
命令で指定した値よりも優先された。
全体的にいえるのは、
dotenv
は一番優先度が低い (定義済のシステム環境変数を優先する・dotenv
の仕様どおり)- Docker イメージ作成時に指定した値より、Docker コンテナ生成・実行時に注入する値の方が優先される
ということであろう。
オプションを併用したらどうなるか?
ココまでで大体分かってきた気はするが、もう少し検証。次は docker run
時に複数のオプションを併用した場合にどうなるか、見てみる。
1. ボリュームマウント + ファイルから注入 → ボリュームマウント < ファイルから注入
$ docker run --rm --volume="$(pwd)/docker-volume:/app/env-dir" --env-file='./docker-env-file/ENV-FILE' practice-env:0`
--volume
と --env-file
。コレは dotenv
仕様のとおり、ボリュームマウントされた .env
も読み込まれはするが、--env-file
の値の方が優先される。
2. ボリュームマウント + オプション引数から注入 → ボリュームマウント < オプション引数から注入
$ docker run --rm --volume="$(pwd)/docker-volume:/app/env-dir" --env 'ENV_1=from env option' practice-env:0
--volume
と --env
の比較。結果は --env-file
の時と同じく、--env
で指定した値の方が優先される。
3. ファイルから注入 + オプション引数から注入 → ファイルから注入 < オプション引数から注入
$ docker run --rm --env-file='./docker-env-file/ENV-FILE' --env 'ENV_1=from env option' practice-env:0
--env-file
と --env
を併用した場合は、--env
で指定した値の方が上書き優先される。
オプション引数の指定順は関係なく、--env-file
よりも --env
の方が優先された。コレは Docker の仕様どおりである。
これらの3つのフラグに関係なく、
--env-file
が始めに処理され、その後-e
と--env
フラグが処理されます。この方法は、必要な時に-e
と--env
で変数を上書きするために使えます。
大部分は --env-file
で指定したファイルから読み込ませたいが、一部だけ変えたい、という時に、ファイルを書き換えるのではなく実行時のコマンドで上書きできるようにしてあるワケだ。
4. ボリュームマウント + ファイルから注入 + オプション引数から注入 → ボリュームマウント < ファイルから注入 < オプション引数から注入
全てを併用した時も一応確認。
$ docker run --rm --volume="$(pwd)/docker-volume:/app/env-dir" --env-file='./docker-env-file/ENV-FILE' --env 'ENV_1=from env option' practice-env:0
それぞれの手法で、別々の名前の環境変数を設定しているのであれば、いずれも正しく適用される。
同じ名前の環境変数に対する設定が衝突する場合は、--volume
より --env-file
、--env-file
より --env
の値の方が優先される。
まとめ
というワケで、Docker + dotenv
における環境変数の適用順は、次の順番で順に定義され、上書きされていくモノと思うと良いだろう。
dotenv
が読み込む、Docker イメージ内の.env
ファイルdotenv
が読み込む、ボリュームマウントして差し替えた.env
ファイル- 「1.」の
.env
ファイルと完全差し替え - 実際には「
dotenv
が指定した値が上書きされる」のではなく、「システム環境変数が存在したらdotenv
が上書きしないように処理している」ワケだが…
- 「1.」の
- Dockerfile 中に書いた
ENV
命令 --env-file
オプション--env
オプション
dotenv
による注入を抜きにすれば、ENV
命令 < --env-file
< --env
の順で上書きされることを覚えておけば良い。
全体的には、「イメージ内に含まれるモノ」<「コマンド実行時の指定」 な順だが、その中でも --env-file
と --env
は、より「その場限り感が強い方」の値を優先されるワケだ。
コレで覚えましたし。