Edited at

Docker で環境変数をホスト OS 側からゲスト OS (コンテナ)に渡す方法(各種)


ホスト OS 側から Docker のコンテナ(ゲスト OS)内のアプリやスクリプトにシステム環境変数を渡したい。アクセストークンとか SSH の鍵とか。


docker 環境変数 ホストOS ゲストOS 渡す 方法 各種」で Qiita 記事に絞ってググっても、まとまったものがなかったので、自分のググラビリティとして。


TL;DR(概要)


docker run コマンド



  • コマンドの引数で環境変数をコンテナに渡す



    • -e --env オプションを使う


      • docker run -e [変数名]=[値] [その他のオプション] [イメージ名] [コマンド]



    • ホスト OS 側の環境変数も利用可能


      • docker run -e [ホスト側変数名] [その他のオプション] [イメージ名] [コマンド]






  • コマンドの引数に外部ファイルを指定して環境変数を渡す



    • --env-file オプションを使う


      • docker run --env-file [ファイルパス] [その他のオプション] [イメージ名] [コマンド]






docker build コマンド



  • ビルド時にコマンド引数で Dockerfile に環境変数を渡す



    • --build-arg オプションを使う


      • docker build --build-arg [変数名]=[値] .


      • 上記の変数名を Dockerfile の ARG に記載すると Dockerfile 内で環境変数として使える。


        変数my_app_version(値は$hoge)をDockerfileに渡す

        $ hoge='v1.0.0'
        
        $ docker build --build-arg my_app_version=$hoge .


        Dockerfile

        ...
        
        ARG my_app_version
        ENV VERSION_APP=$my_app_version
        ...





    • 注意:ビルドされたイメージに変数の値が埋め込まれます。ビルド時のアーキテクチャ名など、センシティブな値でないものに利用します。アクセストークンなどには使わないことをおすすめします。





Dockerfile ファイル



  • Dockerfile に環境変数を記載(設定)してコンテナに渡す



    • ENV 命令(宣言文)を使う


      • ENV [変数名1]=[値1] [変数名2]=[値2] ... [変数名n]=[値n]

      • ENV [変数名] [値]

      • イメージに焼かれるので注意

      • ホスト OS 側の環境変数を直接は利用はできないが docker build 時に ARG 経由で受け取ることができる。(上記 docker build 参照)






  • 環境変数を外部ファイルで渡す


    • 直接的な外部のファイル渡しはない

    • 別途コンテナ内のスクリプトでファイルを読み込む工夫が必要




docker-compose run コマンド



  • 環境変数を引数で渡す



    • -e オプションを使う

    • docker-compose run -e [変数名]=[値] [その他のオプション] [サービス名] [コマンド]

    • ロング・オプション(--env)はない




  • 環境変数を外部ファイルで渡す


    • オプションによる外部のファイル渡しはない


    • docker-compose.yml 経由では可能




docker-compose.yml ファイル



  • 環境変数を設定して渡す



    • environment: 変数に設定する

    • ホスト OS 側の環境変数も利用可能




  • 環境変数を外部ファイルで渡す



    • env_file: 変数にパスを設定する

    • ホスト OS 側の環境変数も利用可能



動作確認済み環境


動作確認済み環境


  • macOS Mojave(OSX 10.14.4)


docker_v18.09.0

$ docker version

Client: Docker Engine - Community
Version: 18.09.0
API version: 1.39
Go version: go1.10.4
Git commit: 4d60db4
Built: Wed Nov 7 00:47:43 2018
OS/Arch: darwin/amd64
Experimental: false

Server: Docker Engine - Community
Engine:
Version: 18.09.0
API version: 1.39 (minimum version 1.12)
Go version: go1.10.4
Git commit: 4d60db4
Built: Wed Nov 7 00:55:00 2018
OS/Arch: linux/amd64
Experimental: false



docker-compose_v1.23.2

$ docker-compose version

docker-compose version 1.23.2, build 1110ad01
docker-py version: 3.6.0
CPython version: 3.6.6
OpenSSL version: OpenSSL 1.1.0h 27 Mar 2018




TS;DR(詳細)


🐒 Docker では通常「ホストOS」「ゲストOS」という呼び方はしません。ここではわかりやすくするため、Docker のエンジン本体を走らせている OS を「ホストOS」、Docker のベース・イメージ(コンテナ内の OS)を「ゲストOS」と呼んでいます。


Alpine OS のイメージの場合、デフォルトの環境変数は以下のようになります。この環境変数に任意の環境変数をコンテナ起動時に指定したいのです。


デフォルトの「環境変数」の確認(AlpineOSコンテナ内の場合)

$ # Default env(現在の環境変数を出力して終了)

$ docker run --rm alpine env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=????????????
HOME=/root


  • 上記の docker run --rm alpine env の簡易説明



    • docker run コマンド: 後述のイメージからコンテナを作成&起動しています。


    • --rm オプション: 実行終了後にコンテナを削除する指定。(わかりづらいですが、ロングオプション -- です。)


    • alpine: 起動するコンテナのイメージ名を指定しています。ここでは Alpine OS のシンプルな Docker イメージを指定しています。


    • env: コンテナで実行するコマンドを指定しています。ここでは現在の環境変数を一覧で確認する Linux/Unix の env コマンドを指定しています。




docker run 時に環境変数を渡す方法


コマンドの引数で環境変数を渡す


-e --env オプションで変数を引数渡し

ホスト OS の環境変数名で指定も可能(下記の場合 MY_SECRET



環境変数を引数で渡す例

$ # ホスト OS 側で環境変数をセット

$ export MY_SECRET='MySecretIsHimitsu'
$
$ # 任意の環境変数を --env ロング・オプションで渡す(-e オプションでも可)
$ docker run --rm \
> --env HOGE="fuga" \
> --env foo="bar" \
> --env MY_SECRET \
> alpine \
> env

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=b08ff260c4b4
HOGE=fuga
foo=bar
MY_SECRET=MySecretIsHimitsu
HOME=/root


コマンドで外部ファイルを指定して環境変数を渡す


--env-file オプションで変数をファイル渡し



環境変数をファイルで渡す例

$ # 変数を記載したファイルの用意

$ cat ./my_env_file.txt
HOGE='FUGA'
foo=bar
$
$ # 引数ファイルを指定して環境変数を確認
$ docker run --rm \
> --env-file ./my_env_file.txt \
> alpine \
> env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=b7b5301cba78
HOGE='FUGA'
foo=bar
HOME=/root


Dockerfile 経由で環境変数を渡す方法


Dockerfile に記載した環境変数をコンテナイメージに渡す


ENV 命令(宣言文)で変数の指定渡し


$ ls

Dockerfile
$ # Dockerfile の内容確認
$ cat Dockerfile
FROM alpine
# 複数を同時指定
ENV HOGE='FUGA' PIYO='PIYO PIYO'
# 個別に指定
ENV hoo bar

$ # イメージのビルド
$ docker build --tag my_alpine .
...(省略)
$
$ # ビルドしたイメージのコンテナ内の環境変数を確認
$ docker run --rm my_alpine env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=c1f67f6ff882
HOGE=FUGA
PIYO=PIYO PIYO
hoo=bar
HOME=/root



  • 注意①:Dockerfile に記載すると環境変数をイメージに焼き付けることになります。アクセストークンやパスワードなど流出すると、たいへんな事態になる変数の場合は注意します。
    この環境変数は -e --env オプションで上書きできます。そのため、Dockerfile に記載する ENV の変数はデフォルト値として設定し、利用時に上書きするのがベターな方法です。


  • 注意②Dockerfile 内でホスト側の環境変数は直接呼び出せません
    ENV my_path=$PATH と指定した場合は、ビルド時のゲスト OS 側の環境変数が使われます。ホスト側の環境変数を使いたい場合は、次項を参照ください。


Dockerfile 「に」環境変数を渡す(docker build時)


--build-argARG 受け取り。そして ENV に渡す。


前述の ENV 宣言方式で Dockerfile に記載した変数の値をコンテナに渡すことができましたが、動的な値を渡したい場合があると思います。例えばバージョン番号などです。特に git でタグ付けした git describe --tags で取得できるバージョン情報を渡したいことも多いのではないでしょうか。

このように、ホスト OS 側からコンテナ(ゲスト OS)に環境変数を渡すには後述する docker-compose コマンドを利用するのが楽です。

しかし、docker-compose コマンドを使いたくない場合は、docker build 時に --build-arg オプションを指定すると、Dockerfile 内で ARG で受け取ることができます。

$ cat Dockerfile

FROM alpine
ARG my_app_version
ENV VERSION_APP=$my_app_version

$ hoge='fuga'
$ docker build --tag my_alpine --build-arg my_app_version=$hoge .
...
$ docker run --rm my_alpine env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=3706b1c92dc5
VERSION_APP=fuga
HOME=/root


Dockerfile で外部ファイルの環境変数を渡す


Dockerfile 自体は外部ファイルの読み込みをサポートしていません


どうしても Dockerfile で外部ファイルから環境変数を設定したい場合



ネットには環境変数を設定するスクリプトを COPY もしくは ADD 命令でコピーし実行させるなどの方法がありますが、オススメしません。

やはりイメージに書き込まれることになるため、設定値が docker inspect [イメージ名] などで見えてしまうからです。

どうしても Dockerfile で完結させたい場合は、-v--volume オプションで環境変数の設定ファイルをマウントさせて、スクリプトで実行させる必要があります。しかし、Dockerfile と同じ階層に置かないといけないなどの Docker の制限などもあり、メンテナンス性がすこぶる悪くなります。

逆に言えば、Dockerfile のディレクトリ内で完結できるということでもあるので、以下で何をしているのかがわかり、スクリプト群も外部に一般公開しないのであれば、シンプルと言えばシンプルかもしれません。


外部ファイルをボリュームとしてマウントし環境変数を設定する

$ # 最低限必要なファイル

$ ls
Dockerfile run_something.sh
$ # Dockerfile の中身確認
$ cat Dockerfile
FROM alpine
ENTRYPOINT /my_volume/run_something.sh

$ # 環境変数を設定するスクリプトの中身(コンテナ起動時に実行される)
$ cat run_something.sh
#!/usr/bin/env sh
export HOGE=FUGA
export foo=bar
export MY_SECRET='MySecretIsHimitsu'
env

$ # イメージのビルド
$ docker build -t my_alpine .
...(省略)
Successfully tagged my_alpine:latest
$
$ # マウントさせずにコンテナを起動した場合の環境変数を確認
$ docker run --rm --entrypoint 'env' test
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=efba0e3f7a89
HOME=/root

$ # カレントディレクトリを /my_volume にマウントさせてコンテナ起動
$ docker run --rm -v $(pwd):/my_volume my_alpine
HOGE=FUGA
HOSTNAME=715cc5bd1fa4
SHLVL=2
HOME=/root
foo=bar
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
MY_SECRET=MySecretIsHimitsu


実は Dockerfile の外部ファイル読み込みとしての INCLUDEIMPORT 命令の実装要望は長いこと(2013 年から)ディスカッションされていましたが却下されました。

これは、Dockerfile は特定のイメージを作成することに特化したものであり、INCLUDEIMPORT 命令をサポートすると依存が増えることになるため、シンプルさに欠けるからのようです。

そのため、docker-composevagrant1 といったオーケストレーション系のソフト経由で設定することをオススメします。

なお、間違えやすいコマンドに docker import がありますが、これは docker pull の代わり、つまり DockerHub 上にないイメージを利用したい場合にアーカイブされた Docker イメージをインポートするコマンドです。




docker-compose run 時に変数を渡す方法


コマンドの引数で環境変数を渡す


-e オプションで変数を引数渡し




  • docker-compose コマンドには --env のロング・オプションはありません。


docker-compose実行時に引数で渡す例

$ # docker-compose.yml の内容確認

$ # 環境変数を何も指定していない点に注目
$ cat docker-compose.yml
version: "3"
services:
my_app:
image: alpine

$ # デフォルトのコンテナ内の環境変数確認
$ docker-compose run --rm my_app env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=d7df4af36a24
TERM=xterm
HOME=/root

$ # 環境変数をオプションで引数渡し
$ docker-compose run \
> --rm \
> -e HOGE=FUGA \
> -e foo=bar \
> my_app \
> env

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=33dd92872107
TERM=xterm
HOGE=FUGA
foo=bar
HOME=/root



コマンドで外部ファイルを指定して環境変数を渡す


docker-compose.ymlenv_file:変数をファイル渡し


docker-compose run のオプションには外部ファイルの指定がありませんが、docker-compose.ymlenv_file: 変数にファイルのパスを指定すると環境変数を外部ファイルで設定することができます。これにより、docker-compose up などでも外部ファイルで環境変数を指定することができます。(次項参照)


docker-compose.yml 経由で変数を渡す方法


docker-compose.yml に記載した環境変数を渡す


environment: 変数で環境変数を指定渡し

ホスト OS の環境変数名で指定も可能(下記の場合 MY_SECRET



docker-compose.ymlの設定例(配列形式で渡す)

version: "3"

services:
app:
image: alpine
environment:
HOGE: FUGA
foo: bar
MY_SECRET:

実行例




実行例(配列形式の例)

$ # docker-compose.ymlの用意

$ ls
docker-compose.yml
$ cat docker-compose.yml
version: "3"
services:
my_app:
image: alpine:latest
environment:
HOGE: FUGA
foo: bar
MY_SECRET:

$ # ホスト OS 側で環境変数をセット
$ export MY_SECRET='MySecretIsHimitsu'
$
$ # オプション引数を渡さずにコンテナ内の環境変数を確認
$ docker-compose run --rm my_app env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=8cbcd1863cd2
TERM=xterm
HOGE=FUGA
foo=bar
MY_SECRET=MySecretIsHimitsu
HOME=/root





docker-compose.ymlの設定例(辞書形式で渡す)

version: "3"

services:
app:
image: alpine
environment:
- HOGE=FUGA
- foo=bar
- MY_SECRET

実行例




実行例(辞書形式の例)

$ # docker-compose.yml を用意

$ ls
docker-compose.yml
$ cat docker-compose.yml
version: "3"
services:
my_app:
image: alpine:latest
environment:
- HOGE=FUGA
- foo=bar
- MY_SECRET

$ # ホスト OS 側で環境変数をセット
$ export MY_SECRET='MySecretIsHimitsu'
$
$ # オプション引数を渡さずにコンテナ内の環境変数を確認
$ docker-compose run --rm my_app env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=961a6c9b0ca6
TERM=xterm
HOGE=FUGA
foo=bar
MY_SECRET=MySecretIsHimitsu
HOME=/root




  • 注意docker-compose.yml のインデントやスペースの入れ方を間違えると、must be a mapping not an arrayエラーが出やすいので注意。

    ERROR: In file './docker-compose.yml', service 'environment' must be a mapping not an array.
    


  • environment | Version3 | Compose file reference | Docker Compose @ docs.docker.com





docker-compose.yml に記載した外部ファイルの環境変数を渡す


env_file: 変数で環境変数を指定渡し

ホスト OS の環境変数名で指定も可能(下記の場合 MY_SECRET


$ # 最低限のファイル一覧

$ ls
docker-compose.yml my_env_file.env
$
$ # docker-compose.yml の内容確認
$ cat docker-compose.yml
version: "3.7"
services:
my_app:
image: alpine
env_file: my_env_file.env

$ # ホスト OS 側で環境変数をセット
$ export MY_SECRET='MySecretIsHimitsu'
$
$ # 変数を記載したファイルの用意(MY_SECRET はホスト OS の環境変数)
$ cat my_env_file.env
HOGE=FUGA
foo=bar
MY_SECRET

$ # 外部ファイル読み込み後の環境変数確認
$ docker-compose run --rm my_app env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=d324b0014cfb
TERM=xterm
HOGE=FUGA
foo=bar
MY_SECRET='MySecretIsHimitsu'
HOME=/root


TS;DR(所感)


コンテナ内のアプリに、アクセス・トークンをハード・コーディングせずに渡したい


Docker の、コンテナ内のアプリやスクリプトからセンシティブな値、つまり触る際に注意が必要な値を扱う必要がありました。アクセス・トークン、SSH や OpenSSL に必要な秘密鍵などの、どんな場合においてもハード・コーディングする(プログラム内に書いちゃう)のはご法度な設定値です。

そのため、一般的なアプリの場合は呼び出す時に引数で渡すか、外部ファイルとして読み込ませるのが定石とされます。

これは Docker の場合でも同じです。しかし、Docker の場合はコンテナごとにシンプルな機能のみを実装させて使うことが多くあります。そのため、すべてのコンテナでアプリの仕様や引数などの書式の決定や統一、ルールの徹底や実装が面倒なのです。(テストするのもたいへん)

システム環境変数に値を設定して利用するのが仕様決めだけでなく実装も楽ではあります。しかし、一般的なアプリの場合、セキュリティの観点からセンシティブ・データの環境変数経由での受け取りは推奨されません。他のアプリからも見れてしまうためです。

しかし Docker の場合は基本的に(意図して設定しない限り)ホスト OS や他のコンテナの設定値やファイルにはアクセスできません。

そこで、ホスト OS 側からコンテナが必要な変数のみを環境変数で指定して、コンテナ内のスクリプトやアプリから環境変数を参照させることで、仕様や実装をシンプルにさせたいと思いました。

調べてみたところ、いくつか方法があり、適材適所で使えば仕様も実装もシンプルになりそうです。知っていたはずの設定もあり、忘れちゃうこと必至なので、まとめてみました。