Docker と Docker Compose の環境変数 - Part 1. ARG と ENV





<!--

part 1 Docker
part 2 Docker Compose

にわける

1. Dockerfile, compose.yaml
1.1. Docker インストラクション
1.2. 変数
2. Linux コマンド

にわける

`source` コマンドでまとめて環境変数を設定しようとしてでできなくて盛大に躓いたため、Docker で環境変数を設定する際の挙動について整理しました。

Docker の ENV っていろいろな指定方法があると思いますがほかにも列挙してください。

ENV VAR=VAL
ENV VAR="VAL"
ENV VAR="VAL_${VAL3}"
https://claude.ai/chat/c09dc17b-e38b-4b31-9784-c7d98f05602e

Dockerfile内で使用する変数の書式は次の通りです：

- 直接置換
  - `${VAR}` -> `VAR`の値
- デフォルト値
  - `${VAR:-default}` -> `VAR`が設定されていて空でない場合は`VAR`の値、それ以外の場合は`default`
  - `${VAR-default}` -> `VAR`が設定されている場合は`VAR`の値、それ以外の場合は`default`
- 必須値
  - `${VAR:?error}` -> `VAR`が設定されていて空でない場合は`VAR`の値、それ以外の場合はエラーで終了
  - `${VAR?error}` -> `VAR`が設定されている場合は`VAR`の値、それ以外の場合はエラーで終了
- 代替値
  - `${VAR:+replacement}` -> `VAR`が設定されていて空でない場合は`replacement`、それ以外の場合は空
  - `${VAR+replacement}` -> `VAR`が設定されている場合は`replacement`、それ以外の場合は空

また、Dockerfile内で環境変数を宣言する正しい形式は `ENV key=value` と `ARG key=value` です。ここで、変数名（`key`）と値（`value`）は等号（`=`）で区切られます。

詳細は以下のリンクを参照してください：
- [Dockerfile reference - Environment replacement](https://docs.docker.com/reference/dockerfile/#environment-replacement)
- [LegacyKeyValueFormat - Description](https://docs.docker.com/reference/build-checks/legacy-key-value-format/#description)

 -->



## はじめに

### 1. Docker インストラクション

* [`ENV`](https://docs.docker.com/reference/dockerfile/#env)
    * 一度設定すれば以降は使える
* [`ARG`](https://docs.docker.com/reference/dockerfile/#arg)
    * 一度設定すれば以降は使える、ただし `CMD`, `ENTRYPOINT` を除く
* `RUN export`, `RUN source`
    * 設定した時だけ使える

<details><summary>インストラクションってなに？</summary>

公式ドキュメントで Dockerfile 内に書く `ARG`, `ENV`, `RUN` などを総称してインストラクション, instruction と表記していました。

* https://docs.docker.com/reference/dockerfile/?highlight=instruction

</details>

<br />



### 2. Linux コマンド

1. `docker image build`
    1. キーバリュー指定
        * `--build-arg`
    2. ファイル指定
        * ない
2. `docker container run`
    1. キーバリュー指定
        * `--env`
    2. ファイル指定
        * `--env-file`

### ◯ サンプルコード

ディレクトリの名前と問題が対応しています

```
gh repo clone domodomodomo/docker-env-sample
cd docker-env-sample/part1/1

# for Bash - macOS, Ubuntu
bash cmd.sh

# for PowerShell - Windows
powershell ./cmd.ps1
```

* https://github.com/domodomodomo/docker-env-sample



## 問題 1 - Docker インストラクション

### ◯ 設問

`echo コマンド` の実行結果 `A`, `B`, `C` にはそれぞれ何が表示されますか？
```Dockerfile:Dockerfile
FROM debian:12.5-slim

ARG BLUE
ARG RED="Charmander"

# 1. RUN export
RUN export GREEN="Bulbasaur" && \
    echo "'${BLUE}' | '${RED}' | '${GREEN}'"
#
# A
#


# 2. ARG
RUN export GREEN="Bulbasaur"
RUN echo "'${BLUE}' | '${RED}' | '${GREEN}'"
#
# B
#


# 3. ENV
ENV BLUE="Squirtle"
CMD echo "'${BLUE}' | '${RED}' | '${GREEN}'"
#
# C
#
```
```
docker image build --build-arg BLUE="Squirtle" --tag app --progress=plain --no-cache .
# --progress=plain 
#     これが無いと RUN echo コマンドが表示されなくなる
# 
# --no-cache
#     これが無いと 1 回目の実行結果がキャッシュされて
#     2 回目、3 回目コマンドを打ったときにRUN echo が実行されなくなる

docker container run app
```


### ◯ 選択


```
1. 
   'Squirtle' | '' | ''
    
2. 
   'Squirtle' | 'Charmander' | ''

3.
   'Squirtle' | 'Charmander' | 'Bulbasaur'
```

### ◯ 解答

<details><summary>解答</summary>

```dockerfile:Dockerfile
FROM debian:12.5-slim

ARG BLUE
ARG RED="Charmander"



# 1. RUN export
RUN export GREEN="Bulbasaur" \
    && echo "'${BLUE}' | '${RED}' | '${GREEN}'"
#
# 'Squirtle' | 'Charmander' | 'Bulbasaur'
#



# 2. ARG
RUN export GREEN="Bulbasaur"
RUN echo "'${BLUE}' | '${RED}' | '${GREEN}'"
#
# 'Squirtle' | 'Charmander' | ''
#
#   GREEN is not saved as an environment variable
#   Each RUN command has a different shell session



# 3. ENV
ENV BLUE="Squirtle"
CMD echo "'${BLUE}' | '${RED}' | '${GREEN}'"
#
# 'Squirtle' | '' | ''
#
#   RED is not saved as an environment variable
#   Environment variables set by ARG command are not applied to CMD

```

</details>

### ◯ シェルセッション

<details><summary>詳細</summary>



* Docker は `RUN` コマンドごとにシェルセッションがわかれているらしいです
    * 根拠となる公式ドキュメントは見つけられませんでした...
* なので `RUN export` しても次の `RUN` では忘れられてしまう
* `COPY`, `ADD` はわかるけど `ENV`, `ARG` はなんでわざわざ別にあるんだろう？🤔って思ってたのですが、そういうことだったんですね...

| Docker インストラクション | Linux コマンド |
|:-:|:-:|
|ENV, ARG|export, source|
|WORKDIR|cd|
|USER|su|
|SHELL|sh, bash|

<br />

* シェルセッションとはなにかについて調べてみたのですがわかりませんでした...orz
    * [What's the shell session? - Ask Ubuntu](https://askubuntu.com/questions/596420/whats-the-shell-session)
    * [What is the definition of a "session" in linux? - Super User](https://superuser.com/questions/651111/what-is-the-definition-of-a-session-in-linux)
    * [shell session - Google 検索結果](https://www.google.com/search?q=shell+session)
* シェルとはなにかに関する記事... 読んでもわからない... orz
    * https://x.com/WebEngrChild/status/1800768651903340967

</details>


### ◯ ARG とキャッシュ

<details><summary>詳細</summary>


`ARG` で定義した環境変数が同じ場合、キャッシュが使われます。サンプルコードで `--no-cache` を指定したのはこの挙動を避けるためです。

> `Dockerfile` で定義された `ARG` 変数の値が前回のビルドと異なる場合、その値が最初に使用されたときに「キャッシュミス」が発生します（定義されたときではありません）。
> If a Dockerfile defines an ARG variable whose value is different from a previous build, then a "cache miss" occurs upon its first usage, not its definition.
> [Impact on build caching - Docker Docs](https://docs.docker.com/reference/dockerfile/#impact-on-build-caching)

> `ARG` を直接使わない `RUN` にもキャッシュミスを起こすので、`ARG` は使用する `RUN` の直前で書いたほうが良い。
> [`Dockerfile` の `ARG` はビルドキャッシュにどう影響するのか、どこに書くべきなのか - Akashic Records](https://a-records.info/docker-arg-run-cache-miss/)

</details>






<br />



### ◯ 疑問


<details><summary>プロンプト</summary>

ChatGPT 4o 先生に質問したところそれっぽい答えを返してくれますが理解はできませんでした。ChatGPT 10 の登場を待ちたいと思います。


```md:プロンプト
# 疑問 1. なぜ `ENV` コマンドがあるのですか？

1. なぜ Dcoker はシェルセッションを `RUN` コマンドごとにわけたのですか？
2. もし Docker に `ENV` コマンドが存在せず、
   `RUN` コマンドごとにシェルセッションが別れておらず、
   一度 `RUN export` すると以降その環境変数が参照できるような場合、
   どのような不都合が生じますか？
```

<!--
`RUN export` を使用すればよかったのではないですか？ENV コマンドを用意した理由はシェルセッションを RUN コマンドごとにわけているからだと思っているのですが、１つにまとめたほうが自然ではないですか？
  -->

```md:プロンプト
# 疑問 2. なぜ `ARG` コマンドがあるのですか？

1. なぜ `ARG` で設定された環境変数は `CMD` では参照できないようになっているのですか？
2. もし Docker に `ARG` コマンドが存在せず `--build-arg` で指定した値が
   そのまま `ENV` で指定された環境変数に代入される場合、どのような不都合が生じますか？
```

<!--
ARG と ENV の機能を１つに纏めてしまえばよかったのではないですか？`--build-arg` で指定された環境変数は ARG ではなく ENV として値が設定されたものにすればよかったのではないのですか？１つにまとめるとなにか不都合なことがあるのですか？
  -->

```md:プロンプト
# 疑問 3. `ENV` や `ARG` で設定された環境変数はどこで保存されていますか？

1. Docker イメージが持つメタ情報
   * `RUN` コマンドが実行される度に
     Docker が OS のシェルセッションに環境変数と値を書き込む
2. Docker イメージが持つ OS
   * `RUN` コマンドが実行される度に
     OS が OS のシェルセッションに環境変数と値を書き込む

場所は可能な限り **具体的に** 教えてください。
```

</details>


## 問題 2 - Linux コマンド

### ◯ 設問

Docker コンテナのみに環境変数を書き込み Docker イメージに環境変数を残さない方法として誤っているものは `A`, `B`, `C` のうちどれですか？


```dockerfile:Dockerfile
FROM debian:12.5-slim

# A ENV GREEN="Bulbasaur"
ENV GREEN="Bulbasaur"

RUN echo "'${BLUE}' | '${RED}' | '${GREEN}'"

CMD echo "'${BLUE}' | '${RED}' | '${GREEN}'"
```
```:.env.local
RED=Charmander
```

<details><summary>コマンド</summary>


<details><summary>PowerShell - Windows</summary>


```powershell:cmd.ps1
docker image build `
    --tag app `
    --progress=plain `
    --no-cache `
    .

# Docker イメージの環境変数を表示
docker image inspect app `
    --format '{{range .Config.Env}}{{println .}}{{end}}'

# B --env BLUE=Bulbasau
# C --env-file .env.local
docker container run `
    --env BLUE=Bulbasaur `
    --env-file .env.local `
    --name app `
    app

# Docker コンテナの環境変数を表示
docker container inspect app `
    --format '{{range .Config.Env}}{{println .}}{{end}}'

docker container rm app
```

</details>

<details><summary>bash - macOS, Ubuntu</summary>

```bash:cmd.bash
#!/bin/bash


docker image build \
    --tag app \
    --progress=plain \
    --no-cache \
    .

# Docker イメージの環境変数を表示
docker image inspect app \
    --format '{{range .Config.Env}}{{println .}}{{end}}'

# B --env BLUE=Bulbasau
# C --env-file .env.local
docker container run \
    --env BLUE=Bulbasaur \
    --env-file .env.local \
    --name app \
    app

# Docker コンテナの環境変数を表示
docker container inspect app \
    --format '{{range .Config.Env}}{{println .}}{{end}}'

docker container rm app
```



</details>

* [docker image inspect - Docker Docs](https://docs.docker.com/reference/cli/docker/image/inspect/)
* [docker container inspect - Docker Docs](https://docs.docker.com/reference/cli/docker/container/inspect/)

</details>



### ◯ 解答

<details><summary>解答</summary>

`A`

</details>

<br />







<!--

   * OS にシェルセッションは１つだけ存在する

   * OS にシェルセッションは２つ存在する
   * OS はプログラミング言語におけるローカルスコープとグローバルスコープのように
     環境変数もローカルのシェルセッションとグローバルのシェルセッションが存在する


また上記の質問を踏まえ、ARG, ENV で設定した環境変数はビルド時に、
どこに保存されているか具体的にを教えてください。

パスワードを保存するためにシェルセッションをキチンと分けて環境変数を管理しているのかな？と思ったのですが、Docker Compose では環境変数ではなく secrets を使ってくださいとのことでした...

* [Docker Composeの環境変数ではなくsecretsで秘密情報を扱う - Qiita](https://bit.ly/4c739Xa)

-->



## セキュリティ

ビルド時にパスワードなどの機密情報を取り扱う際、


:::note alert
環境変数
:::


ではなく

:::note info
ファイル
:::


を通してやり取りしてくださいという記述が見られます。まだ整理できていません...orz がとり急ぎ大事そうな記事を列挙させていただきます。

* "環境変数の方法" より推奨される "ファイルの方法" による機密情報の受け渡し方法
    * Docker
        * `RUN --mount=type=secret` オプション
    * Docker Compse
        * `secrets` プロパティ 


### ◯ Docker

> シークレット（ユーザー資格情報、APIトークンなど）を渡すためにビルド引数(訳注: `--build-arg`)を使用することは推奨されません。ビルド引数は `docker history` コマンドで見える ... (中略) ... Docker イメージをビルドする際にシークレットを安全に使用する方法については、`RUN --mount=type=secret` セクションを参照してください。
> It isn't recommended to use build arguments for passing secrets such as user credentials, API tokens, etc. Build arguments are visible in the docker history command and ... (omitted) ... Refer to the `RUN --mount=type=secret` section to learn about secure ways to use secrets when building images.
>
> [ARG - Docker Docs](https://docs.docker.com/reference/dockerfile/#arg)

### ◯ Docker Compose

> Diogo Monicaの[記事](https://diogomonica.com/2017/03/27/why-you-shouldnt-use-env-variables-for-secret-data/)では、環境変数で秘密情報を扱う問題点として
>
> - 環境はプロセスで暗黙的に利用可能のため、アクセスや利用を追跡することが困難である。
> - アプリケーションが環境をデバッグやエラー報告のために出力することはよくある。
> - 環境変数は子プロセスに引き継がれるため、意図しないアクセスが可能になる。これは、最小権限の原則を破る。
> - アプリケーションがクラッシュした時、デバッグのために、環境変数をログファイルに保存することは一般的であり、これはディスク上に平文の秘密情報があることを意味する。
> - 環境変数に秘密情報を入れると、それはすぐにtribal knowledgeになる。特定の環境変数がデリケートをであると認識していない新人のエンジニアは、適切に処理できない。
>
> [Docker Composeの環境変数ではなくsecretsで秘密情報を扱う - Qiita](https://qiita.com/myabu/items/89797cddfa7225ff2b5d)

## おわりに






### ◯ シリーズ

* [Part 1. ARG と ENV - Qiita](https://bit.ly/4aZP3WG)
* [Part 2. オプションとプロパティ - Zenn](https://bit.ly/4aPiivr)
* [Part 3. 全体の流れ - Qiita](https://bit.ly/3Xhc446)

ありがとうございました。