Windows環境における docker-compose の .env と 環境変数の優先順位

## 前提
ドキュメントによると、 `.env` で変数を指定した場合でも環境変数のほうが優先されると書いてある。

> Values in the shell take precedence over those specified in the ` .env ` file.

[Environment variables in Compose | Docker Documentation](https://docs.docker.com/compose/environment-variables/#the-env-file)

> 実行時に環境変数を指定すると、常に .env ファイル中で定義した変数を上書きします。

[環境ファイル — Docker-docs-ja 17.06.Beta ドキュメント](http://docs.docker.jp/compose/env-file.html)

## 背景（不思議な挙動）
環境変数 `http_proxy` が設定されている状態で `.env` に `http_proxy` を別の値で記述すると `.env` の値が優先された[^TZ]。
[^TZ]: 一方、 環境変数 `TZ` は `.env` で上書きされなかった。

ドキュメントの記述と一致していないように感じたので調査した。

## 検証
### 環境
Windows 10 Home + docker-machine

```sh
$ docker.exe --version
Docker version 18.03.0-ce, build 0520e24302

$ docker-compose.exe --version
docker-compose version 1.20.1, build 5d8c71b2

$ docker-machine.exe --version
docker-machine.exe version 0.14.0, build 89b8332

$ docker-machine.exe ssh default docker --version
Docker version 18.09.1, build 4c52b90
```

### `docker-compose.yml`
```yaml:docker-compose.yml
version: '3'

services:
  test:
    image: alpine:3.9
    command: env
    environment:
      - AAA
      - BBB
      - CCC
      - DDD
      - EEE
      - fff
      - ggg
      - hhh
      - iii
      - jjj
```

### `.env`
```sh:.env
AAA=.env_var
BBB=.env_var
CCC=.env_var
DDD=.env_var
EEE=.env_var

fff=.env_var
ggg=.env_var
hhh=.env_var
iii=.env_var
jjj=.env_var
```

### 環境変数
以下の4つを環境変数として設定

- DDD
- EEE
- iii
- jjj


```sh
$ env | sort | grep env_var
DDD=user_env_var
EEE=system_env_var
iii=user_env_var
jjj=system_env_var
```

※ Windows環境のため、ユーザー環境変数とシステム変数を大文字小文字それぞれ設定

### `test.sh`
以下の変数を `sh` 内で定義[^sh]

- シェル変数
    - `AAA`
    - `fff`
- 環境変数 (`export`[^sh])
    - `BBB`
    - `ggg`

[^sh]: Git Bash で実行

```sh:test.sh
#!/bin/sh -x

cat .env

echo -e "\n\n"

printenv AAA
printenv BBB
printenv CCC
printenv DDD
printenv EEE

printenv fff
printenv ggg
printenv hhh
printenv iii
printenv jjj

echo -e "\n\n"

AAA=sh_var
export BBB=exported_sh_var

fff=sh_var
export ggg=exported_sh_var

echo -e "\n\n"

printenv AAA
printenv BBB
printenv CCC
printenv DDD
printenv EEE

printenv fff
printenv ggg
printenv hhh
printenv iii
printenv jjj

echo -e "\n\n"

docker-compose config
docker-compose up
```

### 実行結果
Git Bash で実行した。

```sh
$ ./test.sh
+ cat .env
AAA=.env_var
BBB=.env_var
CCC=.env_var
DDD=.env_var
EEE=.env_var

fff=.env_var
ggg=.env_var
hhh=.env_var
iii=.env_var
jjj=.env_var
+ echo -e '\n\n'



+ printenv AAA
+ printenv BBB
+ printenv CCC
+ printenv DDD
user_env_var
+ printenv EEE
system_env_var
+ printenv fff
+ printenv ggg
+ printenv hhh
+ printenv iii
user_env_var
+ printenv jjj
system_env_var
+ echo -e '\n\n'



+ AAA=sh_var
+ export BBB=exported_sh_var
+ BBB=exported_sh_var
+ fff=sh_var
+ export ggg=exported_sh_var
+ ggg=exported_sh_var
+ echo -e '\n\n'



+ printenv AAA
+ printenv BBB
exported_sh_var
+ printenv CCC
+ printenv DDD
user_env_var
+ printenv EEE
system_env_var
+ printenv fff
+ printenv ggg
exported_sh_var
+ printenv hhh
+ printenv iii
user_env_var
+ printenv jjj
system_env_var
+ echo -e '\n\n'



+ docker-compose config
services:
  test:
    command: env
    environment:
      AAA: .env_var
      BBB: exported_sh_var
      CCC: .env_var
      DDD: user_env_var
      EEE: system_env_var
      fff: .env_var
      ggg: .env_var
      hhh: .env_var
      iii: .env_var
      jjj: .env_var
    image: alpine:3.9
version: '3.0'

+ docker-compose up
Starting 1_test_1 ... done
Attaching to 1_test_1
test_1  | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
test_1  | HOSTNAME=02a7c6031232
test_1  | AAA=.env_var
test_1  | BBB=exported_sh_var
test_1  | CCC=.env_var
test_1  | DDD=user_env_var
test_1  | EEE=system_env_var
test_1  | fff=.env_var
test_1  | ggg=.env_var
test_1  | hhh=.env_var
test_1  | iii=.env_var
test_1  | jjj=.env_var
test_1  | HOME=/root
1_test_1 exited with code 0
```

`fff` ～ `jjj` が全て `.env` が優先され、 `.env` で定義した `.env_var` になっている。

```
      fff: .env_var
      ggg: .env_var
      hhh: .env_var
      iii: .env_var
      jjj: .env_var
```

ちなみに、Linux環境では、 `AAA` ～ `EEE` と同じように環境変数が優先される。

```
      fff: .env_var
      ggg: exported_sh_var
      hhh: .env_var
      iii: user_env_var
      jjj: system_env_var
```



## おまけ
### 変数名に小文字・数字・記号が入る場合
- 大文字・小文字(`Xx`): `.env` 優先（Linux環境では環境変数優先）
- 大文字・数字(`Y123`): 環境変数優先
- 大文字・記号(`Z_Z`): 環境変数優先

#### `docker-compose.yml`
```yaml
version: '3'

services:
  test:
    image: alpine:3.9
    command: env
    environment:
      - Xx
      - Y123
      - Z_Z
```

#### `.env`
```sh
Xx=.env_var
Y123=.env_var
Z_Z=.env_var
```

#### `test.sh`
```sh
#!/bin/sh -x

cat .env

echo -e "\n\n"

export Xx=exported_sh_var
export Y123=exported_sh_var
export Z_Z=exported_sh_var

echo -e "\n\n"

printenv Xx
printenv Y123
printenv Z_Z

echo -e "\n\n"

docker-compose config
docker-compose up
```

#### 実行結果
```sh:test.sh
$ ./test.sh
+ cat .env
Xx=.env_var
Y123=.env_var
Z_Z=.env_var
+ echo -e '\n\n'



+ export Xx=exported_sh_var
+ Xx=exported_sh_var
+ export Y123=exported_sh_var
+ Y123=exported_sh_var
+ export Z_Z=exported_sh_var
+ Z_Z=exported_sh_var
+ echo -e '\n\n'



+ printenv Xx
exported_sh_var
+ printenv Y123
exported_sh_var
+ printenv Z_Z
exported_sh_var
+ echo -e '\n\n'



+ docker-compose config
services:
  test:
    command: env
    environment:
      Xx: .env_var
      Y123: exported_sh_var
      Z_Z: exported_sh_var
    image: alpine:3.9
version: '3.0'
```

#### Linux環境での実行結果（抜粋）
```
      Xx: exported_sh_var
      Y123: exported_sh_var
      Z_Z: exported_sh_var
```

### 大文字環境変数でも強制的に上書きする
`source ./.env` を実行する

※ これだけLinux環境でも同じ挙動となった

#### `docker-compose.yml`
```yaml
version: '3'

services:
  test:
    image: alpine:3.9
    command: env
    environment:
      - AAA
      - BBB
      - CCC
```

#### `.env`
```sh
AAA=.env_var
BBB=.env_var
```

#### `test.sh`
```Sh
#!/bin/sh -x

export BBB=exported_sh_var
export CCC=exported_sh_var

echo -e "\n\n"

printenv AAA
printenv BBB
printenv CCC

echo -e "\n\n"

source ./.env

echo -e "\n\n"

printenv AAA
printenv BBB
printenv CCC

echo -e "\n\n"

docker-compose config
docker-compose up
```

#### 実行結果
```sh:test.sh
$ ./test.sh
+ export BBB=exported_sh_var
+ BBB=exported_sh_var
+ export CCC=exported_sh_var
+ CCC=exported_sh_var
+ echo -e '\n\n'



+ printenv AAA
+ printenv BBB
exported_sh_var
+ printenv CCC
exported_sh_var
+ echo -e '\n\n'



+ source ./.env
++ AAA=.env_var
++ BBB=.env_var
+ echo -e '\n\n'



+ printenv AAA
+ printenv BBB
.env_var
+ printenv CCC
exported_sh_var
+ echo -e '\n\n'



+ docker-compose config
services:
  test:
    command: env
    environment:
      AAA: .env_var
      BBB: .env_var
      CCC: exported_sh_var
    image: alpine:3.9
version: '3.0'
```

### コンテナ内に渡す環境変数だけ指定したいなら [env_file](https://docs.docker.com/compose/compose-file/#env_file)
`env_file: my.env` のように `docker-compose.yml` に書くことで、 `environment` のように環境変数をコンテナに直接渡せる。

※ `.env` の定義は `docker-compose.yml` 内でも使えるため、完全な代替ではない

#### 優先順位
1. `environment` (環境変数)
1. `environment` (`.env`)
1. `env_file`


#### `docker-compose.yml`
```yaml
version: '3'

services:
  test:
    image: alpine:3.9
    env_file: my.env
    command: env
    environment:
      - BBB
      - CCC
```

#### `.env`
```sh
AAA=.env_var
BBB=.env_var
CCC=.env_var
```

#### `my.env`
```sh
AAA=my.env_var
BBB=my.env_var
CCC=my.env_var
```

#### `test.sh`
```sh
#!/bin/sh -x

cat .env

echo -e "\n\n"

cat my.env

echo -e "\n\n"

printenv AAA
printenv BBB
printenv CCC

echo -e "\n\n"

export CCC=exported_sh_var

echo -e "\n\n"

printenv AAA
printenv BBB
printenv CCC

echo -e "\n\n"

docker-compose config
```

#### 実行結果
```sh:test.sh
$ ./test.sh
+ cat .env
AAA=.env_var
BBB=.env_var
CCC=.env_var
+ echo -e '\n\n'



+ cat my.env
AAA=my.env_var
BBB=my.env_var
CCC=my.env_var
+ echo -e '\n\n'



+ printenv AAA
+ printenv BBB
+ printenv CCC
+ echo -e '\n\n'



+ export CCC=exported_sh_var
+ CCC=exported_sh_var
+ echo -e '\n\n'



+ printenv AAA
+ printenv BBB
+ printenv CCC
exported_sh_var
+ echo -e '\n\n'



+ docker-compose config
services:
  test:
    command: env
    environment:
      AAA: my.env_var
      BBB: .env_var
      CCC: exported_sh_var
    image: alpine:3.9
version: '3.0'
```


## 結論
Windows環境の場合、

- 変数に
    - 小文字が含まれる場合は `.env` のほうが優先される
    - 小文字が含まれない場合（大文字・数字・記号のみの場合）は 環境変数 のほうが優先される
- 小文字が含まれない変数も `.env` を優先させたい場合は `source ./.env` を実行することで環境変数の優先をキャンセルできる
    - ※ [env_file](https://docs.docker.com/compose/compose-file/#env_file) を使ったほうが自然＆楽

Linux環境の場合、

- 変数名中の小文字の有無によらず 環境変数 のほうが優先される
-  `.env` を優先させたい場合は `source ./.env` を実行することで環境変数の優先をキャンセルできる
    - ※ [env_file](https://docs.docker.com/compose/compose-file/#env_file) を使ったほうが自然＆楽