Docker for WindowsをWSLから使う時のVolumeの扱い方

# TL;DR

* Docker for WindowsをWSL（旧Bash on Ubuntu on Windows）から利用する際、バインドマウントする場合は注意が必要。
* Cドライブを共有に設定をしていれば、ホスト側ファイルシステムの`/c`と`/C`にマウントされているので、`/c/Users/hoge/fuga`の形式で参照しよう。
* WSL内のディレクトリ（`/home/hoge/fuga`など）は使えない。

# 環境

* Windows 10 Pro Fall Creators Update
* Docker for Windows Version 17.09.0-ce-win33 (13620)
* WSL:Ubuntu 16.04 LTS + docker 17.09.0-ce + docker-compose version 1.8.0

# 注意

* DockerのVolume周りの知識のある方は読んでも時間の無駄だと思います。
* 間違ってる箇所があればごめんなさい。

# 1. Docker for WindowsをWSLで使う方法

このあたりを参考に
[WSL(Bash on Windows)でDockerを使用する - Qiita](https://qiita.com/yoichiwo7/items/0b2aaa3a8c26ce8e87fe)

ひょっとするとこれでも良いかもしれませんがw
[【小ネタ】Docker for WindowsをWSLで使う一番楽な方法 - Qiita](https://qiita.com/gentaro/items/fd0c32af2b12033f9cae)

# 2. 【復習】PowerShell（またはコマンドライン）でローカルディレクトリをマウントする場合

まずはWSLでなく単純にDocker for WindowsをコマンドラインやPowerShellから普通に使う場合の確認。
例えばMySQLのデータディレクトリ`/var/lib/mysql`を`C:\hoge\fuga`に保存する（**Bind mount**する）には、以下のように指定する。

## 2-1. docker run -vオプションの場合

`-v (ローカルディレクトリ):(コンテナのディレクトリ)`で指定する。

```ps1:PowerShell
PS C:\hoge> docker run -v C:\hoge\fuga:/var/lib/mysql -e "MYSQL_RANDOM_ROOT_PASSWORD=yes;MYSQL_DATABASE=hogedb;MYSQL_USER=admin;MYSQL_PASSWORD=pass" -p 3306:3306 mysql
```

## 2-2. docker-composeの場合
docker-composeを使う場合は、`volumes:`に記述する。

```yaml:docker-compose.yml
version: '2'
services:
  mysql:
    image: mysql
    volumes:
      - C:\hoge\fuga:/var/lib/mysql
    ports: 
      - 3306:3306
    environment:
      - MYSQL_RANDOM_ROOT_PASSWORD=yes
      - MYSQL_DATABASE=hogedb
      - MYSQL_USER=admin
      - MYSQL_PASSWORD=pass
```

```ps1:PowerShell
PS C:\hoge> docker-compose up
```

これで`C:\hoge\fuga`が`/var/lib/mysql`にマウントされる。
基本ですね。

## 2-3. 共有設定について

マウントする際の注意点として、マウントしたいディレクトリは、事前に設定画面の[Shared Drives]でチェックを入れてドライブレベルで共有設定をしておく必要がある。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/86855/51a4282e-c631-0116-ad82-305331c9fbe0.png)


共有していないドライブのディレクトリをマウントしようとすると、以下の画面が出て設定を促される。（`Y:\hoge\fuga`を指定してみた）

![image.png](https://qiita-image-store.s3.amazonaws.com/0/86855/5f16bf86-c8bf-ca8a-3986-bcb98db4f487.png)


# 3. WSLでローカルディレクトリをマウントする場合

ここから本題。

## 3-1. docker run -vオプションの場合

WSLのBashで同じことをしようとした場合、Windowsのパス区切り文字である`\`がBashのエスケープ文字のため、`\\`にする必要がある。

```bash:Bash
$ docker run -v C:\\hoge\\fuga:/var/lib/mysql -e "MYSQL_RANDOM_ROOT_PASSWORD=yes;MYSQL_DATABASE=hogedb;MYSQL_USER=admin;MYSQL_PASSWORD=pass" -p 3306:3306 mysql
```

これで問題なし。
ただし、エスケープが面倒なら`/c/hoge/fuga`でもOK。（理由は後述）

## 3-2. docker-composeの場合

**Windowsのパス形式は使えない。**
`docker-compose.yml`にWindowsのパス形式で記述しているとエラーになる。

```bash:Bash
$ docker-compose up
ERROR: Named volume "C:\hoge\fuga:/var/lib/mysql" is used in service "mysql" but no declaration was found in the volumes section.
```

エラーメッセージを見ると、`C:\hoge\fuga`が**Named volume**（docker volume createコマンドで作成するvolume）として認識されてしまっている。
これを回避するには、docker-compose.ymlを以下のように修正する必要がある。

```diff:docker-compose.yml
version: '2'
services:
  mysql:
    image: mysql
    volumes:
-      - C:\hoge\fuga:/var/lib/mysql
+      - /C/hoge/fuga:/var/lib/mysql
    ports: 
      - 3306:3306
    environment:
      - MYSQL_RANDOM_ROOT_PASSWORD=yes
      - MYSQL_DATABASE=hogedb
      - MYSQL_USER=admin
      - MYSQL_PASSWORD=pass
```

Linuxのdocker-composeが**Windowsのパス形式を理解できない**のが原因。
`docker run -v`の場合と同様、`/c/hoge/fuga`の形式なら利用できるので、変換しておく必要がある。

WindowsのDocker ToolboxやDocker Machineなら環境変数`COMPOSE_CONVERT_WINDOWS_PATHS=1`をセットすれば内部的に`/c/hoge/fuga`のように変換して解釈してくれたが、残念ながらこの方法も使えなかった。
Docker for Windowsのデーモンは`docker run -v`の指定に対してはこの変換を行うものの、docker-composeには対応していないらしい。(たぶん)

なお、`C:\\hoge\\fuga`のようにエスケープしてみても無理だった。

# 4. WSL内のディレクトリの扱い

## 4-1. WSL内のディレクトリは使えない

### ・WSL内のディレクトリを直接指定した場合

例えばWSL内の`/home/hoge/fuga`をマウントしたい場合、そのまま指定すると

```bash:Bash
$ pwd
/home/hoge
$ docker run -v $PWD/fuga:/var/lib/mysql -e "MYSQL_RANDOM_ROOT_PASSWORD=yes;MYSQL_DATABASE=hogedb;MYSQL_USER=admin;MYSQL_PASSWORD=pass" -p 3306:3306 mysql
```

で、一見するとちゃんと動くように見える。
`docker inspect`で確認すると、`/home/hoge/fuga`がバインドされている。

```bash:Bash
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                 NAMES
4884a258cd99        mysql               "docker-entrypoint..."   12 seconds ago      Up 9 seconds        0.0.0.0:3306->3306/tcp                                nervous_banach
$ docker inspect nervous_banach
[
    {
(省略)
        "HostConfig": {
            "Binds": [
                "/home/hoge/fuga:/var/lib/mysql"
            ],
(以下略)
```

ただし、この`/home/hoge/fuga`は**ホスト（仮想マシン）側のディレクトリ**なので、WSL側の`/home/hoge/fuga`にはデータは作成されていない。
（MySQLは起動するだけで様々なファイルが作成されるので、起動後に実際にディレクトリ内の確認するとわかるが、空のまま）

### ・DrvFSの階層を指定した場合

WSLの`/home/hoge/fuga`は、Windowsのディレクトリ構造上は
`C:\Users\hoge\AppData\Local\Packages\CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc\LocalState\rootfs\home\hoge\fuga`
なので、そもそも階層が違う。（Ubuntuの場合）

このパスをWSLから参照する場合、
`/mnt/c/Users/hoge/AppData/Local/Packages/CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc/LocalState/rootfs/home/hoge/fuga`
になる。

この`/mnt`からWindows上のドライブが参照できる仕組みは**DrvFS**というファイルシステムで実現されている。
そこで、これを使って

```bash:Bash
$ docker run -v /mnt/c/Users/hoge/AppData/Local/Packages/CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc/LocalState/rootfs/home/hoge/fuga:/var/lib/mysql -e "MYSQL_RANDOM_ROOT_PASSWORD=yes;MYSQL_DATABASE=hogedb;MYSQL_USER=admin;MYSQL_PASSWORD=pass" -p 3306:3306 mysql
```

としてみても、このパスはdocker側には理解できないため、やっぱりコンテナは**ホスト側のディレクトリ**を参照してしまう。

### ・VolFSの階層を指定した場合

WSLの`/home/hoge/fuga`が
`C:\Users\hoge\AppData\Local\Packages\CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc\LocalState\rootfs\home\hoge\fuga`
であるなら、
`/c/Users/hoge/AppData/Local/Packages/CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc/LocalState/rootfs/home/hoge/fuga`

を指定すれば使えるか？と思いきや、これも上手く行かない。

dockerホスト側はちゃんと
`/c/Users/hoge/AppData/Local/Packages/CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc/LocalState/rootfs/home/hoge/fuga`
に書き込みに行くが、そもそもこの階層はWindowsで直接扱えないため、ネットワーク共有越しにも書き込めない。

そのためか、ホスト上のこの階層にファイルは作られるものの、これらは**ホスト上にのみ**存在することになり、クライアント側には書き込まれない。

正直この辺は調査不足で、具体的にどういう理由でこういう動きをするのかはよくわからない:sweat_smile:

ただ、仮に書き込めたとしても、DockerのサービスがWindows上で上記のフォルダにファイルを作成することになるため、WSL側で読むことができないハズ。

参考：[Windows Subsystem for Linuxのファイルシステムにおける注意点 - Qiita](https://qiita.com/lefb766/items/ca2be2590bd7c22395fd)

…というか、そもそもこんな深い階層を指定したくないわなw

## 4-2. docker-composeで相対パスを指定すると意図しないパスに変換される

上記の通りWSLのディレクトリ構造は使えないのであまり意味はないが、`docker-compose.yml`で相対パスを指定するとおかしな変換がかかる。

```yaml:docker-compose.yml
version: '2'
services:
  mysql:
    image: mysql
    volumes:
      - ./fuga:/var/lib/mysql
    ports: 
      - 3306:3306
    environment:
      - MYSQL_RANDOM_ROOT_PASSWORD=yes
      - MYSQL_DATABASE=hogedb
      - MYSQL_USER=admin
      - MYSQL_PASSWORD=pass
```
```bash:Bash
$ pwd
/home/hoge
$ docker-compose up -d
$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                                                 NAMES
9872e250c33d        mysql               "docker-entrypoint..."   About a minute ago   Up About a minute   0.0.0.0:3306->3306/tcp                                hoge_mysql_1
$ docker inspect hoge_mysql_1
[
    {
(省略)
        "HostConfig": {
            "Binds": [
                "/mnt/c/hoge/fuga:/var/lib/mysql:rw"
            ],
(以下略)
```

`.`が`/mnt/c/hoge`に変換されてしまう。
`/home`を`/mnt/c`と解釈するみたいだが、理由は謎~~だし調べる気も起きない~~。

# 5. そもそもマウントとは

ここまで色々試してみたが、マウントの仕組みを理解していれば結構当たり前の内容かもしれない。
Dockerにおけるマウントは、基本的に以下の三種類ある。

* volume
* bind mount
* tmpfs mount

![image.png](https://qiita-image-store.s3.amazonaws.com/0/86855/2e6847f8-a301-400f-13fe-94ca953c7944.png)
（[Manage data in Docker | Docker Documentation](https://docs.docker.com/engine/admin/volumes/#choose-the-right-type-of-mount)より引用）

細かい説明は省くとして、図で明らかなように、Volumeをマウントするという行為は全て**ホスト側のファイルシステム内**の話。

Docker for Windowsで`docker run -v C:\hoge:/var/hoge ...`しか使った事がないと、何か不思議な力でローカルの`C:\hoge`にコンテナが直接読み書きを行ってくれているように感じるが、dockerのデーモン的には**ホスト内の指定されたディレクトリを直接読み書きしている**だけで、その場所がたまたまネットワーク越しにマウントされていた、と考えるとわかりやすい。

実感を得るためホストである仮想マシンのMobyLinuxVMの中身を確認してみると、[2-3. 共有設定について](https://qiita.com/gentaro/items/7dec88e663f59b472de6#2-3-%E5%85%B1%E6%9C%89%E8%A8%AD%E5%AE%9A%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)で共有にしたドライブがホストの`/`にマウントされていることが確認できる。
（hostenterについては[こちら](https://qiita.com/gentaro/items/cf666259cb6baf2eb8db)）

```Shell-Session:Bash
$ docker run -it --privileged --pid=host hostenter sh
/ # ls -l
total 56
drwxr-xr-x    2 root     root          8192 Nov 28 01:36 C
drwxr-xr-x    2 root     root         16384 Nov 27 13:31 D
drwxrwxrwt    6 root     root           140 Nov 27 13:02 Database
drwxr-xr-x    2 root     root          1680 Sep 27 00:15 bin
drwxr-xr-x    2 root     root          8192 Nov 28 01:36 c
drwxrwxr-x    4 dockrema dockrema        80 Sep 27 00:37 containers
drwxr-xr-x    2 root     root         16384 Nov 27 13:31 d
drwxr-xr-x   12 root     root          2920 Nov 27 13:02 dev
drwxrwxr-x   25 dockrema dockrema      1320 Nov 27 13:02 etc
drwxr-xr-x    3 root     root            60 Nov 28 06:35 home
-rwxrwxr-x    1 root     root           878 Sep 27 00:15 init
drwxr-xr-x    7 dockrema dockrema       700 Sep 27 00:15 lib
drwxr-xr-x    5 root     root           100 Sep 27 00:15 media
drwxr-xr-x    2 root     root            40 Nov 28 08:32 mnt
drwxrwxrwx    1 docker   docker           0 May  4  2006 port
dr-xr-xr-x  145 root     root             0 Nov 27 13:02 proc
drwx------    2 root     root            60 Nov 28 00:27 root
drwxr-xr-x    9 root     root           460 Nov 27 13:02 run
drwxrwxr-x    2 dockrema dockrema      2040 Sep 27 00:15 sbin
drwxr-xr-x    2 root     root            40 Dec 26  2016 srv
dr-xr-xr-x   13 root     root             0 Nov 27 13:02 sys
drwxrwxrwt    5 root     root           100 Nov 29 01:34 tmp
drwxrwxr-x    9 dockrema dockrema       180 Sep 27 00:15 usr
drwxr-xr-x   11 root     root          4096 Oct 19 07:05 var
/ # ls -l /c
total 7260145
-rwxr-xr-x    1 root     root             1 Jul 16  2016 BOOTNXT
drwxr-xr-x    2 root     root          4096 Oct 19 06:11 Documents and Settings
drwxr-xr-x    2 root     root             0 Nov 16  2016 Intel
drwxr-xr-x    2 root     root             0 Feb 15  2017 OneDriveTemp
drwxr-xr-x    2 root     root             0 Sep 29 13:46 PerfLogs
dr-xr-xr-x    2 root     root             0 Nov 21 13:02 Program Files
dr-xr-xr-x    2 root     root             0 Nov 15 13:09 Program Files (x86)
drwxr-xr-x    2 root     root             0 Nov 15 09:09 ProgramData
drwxr-xr-x    2 root     root             0 Oct 19 06:24 Recovery
drwxr-xr-x    2 root     root             0 Oct 26 01:08 System Volume Information
dr-xr-xr-x    2 root     root             0 Oct 19 06:11 Users
drwxr-xr-x    2 root     root             0 Nov 27 13:31 Windows
drwxr-xr-x    2 root     root             0 Nov 27 13:00 _MEI134002
-r-xr-xr-x    1 root     root        384322 Jul 16  2016 bootmgr
-rwxr-xr-x    1 root     root         41388 Oct 19 05:44 downlevel_2017_10_19_13_17_17_700.log
-rwxr-xr-x    1 root     root     6746087424 Nov 27 12:59 hiberfil.sys
drwxr-xr-x    2 root     root             0 Oct 19 05:55 inetpub
-rwxr-xr-x    1 root     root     419430400 Oct 19 05:46 pagefile.sys
-rwxr-xr-x    1 root     root     268435456 Nov 27 12:59 swapfile.sys
/ # cat /etc/mtab
（略）
//10.0.75.1/C /c cifs rw,relatime,vers=3.02,sec=ntlmssp,cache=strict,username=hoge,domain=MyPC,uid=0,noforceuid,gid=0,noforcegid,addr=10.0.75.1,file_mode=0755,dir_mode=0755,iocharset=utf8,nounix,mapposix,nobrl,mfsymlinks,noperm,rsize=1048576,wsize=1048576,echo_interval=60,actimeo=1 0 0
//10.0.75.1/C /C cifs rw,relatime,vers=3.02,sec=ntlmssp,cache=strict,username=hoge,domain=MyPC,uid=0,noforceuid,gid=0,noforcegid,addr=10.0.75.1,file_mode=0755,dir_mode=0755,iocharset=utf8,nounix,mapposix,nobrl,mfsymlinks,noperm,rsize=1048576,wsize=1048576,echo_interval=60,actimeo=1 0 0
//10.0.75.1/D /d cifs rw,relatime,vers=3.02,sec=ntlmssp,cache=strict,username=hoge,domain=MyPC,uid=0,noforceuid,gid=0,noforcegid,addr=10.0.75.1,file_mode=0755,dir_mode=0755,iocharset=utf8,nounix,serverino,mapposix,nobrl,mfsymlinks,noperm,rsize=1048576,wsize=1048576,echo_interval=60,actimeo=1 0 0
//10.0.75.1/D /D cifs rw,relatime,vers=3.02,sec=ntlmssp,cache=strict,username=hoge,domain=MyPC,uid=0,noforceuid,gid=0,noforcegid,addr=10.0.75.1,file_mode=0755,dir_mode=0755,iocharset=utf8,nounix,serverino,mapposix,nobrl,mfsymlinks,noperm,rsize=1048576,wsize=1048576,echo_interval=60,actimeo=1 0 0
```

CドライブとDドライブを共有設定しているので、`/c`,`/C`,`/d`,`/D`がそれぞれマウントされている。
個人的にはドライブレターを大文字小文字を区別しないのが不思議だったが、割りと単純な話だった…。

# Conclusion

* Docker for WindowsをWSLで使いたい場合、Bind mountできるのは通常のWindowsディレクトリ(VolFSはダメ)
* `C:\hoge\fuga`=`/c/hoge/fuga`だという事を意識して、常に後者の指定をしておけばまぁ間違いない
* WSL上で作業していると`C:\hoge\fuga`=`/mnt/c/hoge/fuga`なのでややこしい
* ~~素直にPowerShell使えば良いんじゃ…~~

# TIL

* 散々言及してきた`docker run`の`-v`オプションは推奨されておらず、今は`--mount`オプションが[推奨らしい](https://docs.docker.com/engine/admin/volumes/bind-mounts/#choosing-the--v-or-mount-flag)…。
* `-v`だと存在しないディレクトリ・ファイルは勝手に作るが`--mount`では[エラーにする](https://docs.docker.com/engine/admin/volumes/bind-mounts/#differences-between--v-and---mount-behavior)そうだ。確かにその方が良いな。

（2019/08/21 追記）
　上記の記述はv17.09のドキュメントに書いてあった以下のTipを根拠にしていた。
> Tip: New users should use the --mount syntax. Experienced users may be more familiar with the -v or --volume syntax, but are encouraged to use --mount, because research has shown it to be easier to use.

　現時点の最新であるv19.07のドキュメントでは以下の記載に変わっていて、かなりトーンダウンしているみたい。
> New users should try --mount syntax which is simpler than --volume syntax.

　そのため、今は「`-v`オプションは推奨されておらず」とまでは言えない気がする。
　まぁDocker Hubなんかのコンテナの説明文でも`-v`を使う例ばっかりだし…。