DockerのUID/GIDあれこれ: Linux版通常モード & Docker Desktop for Mac編

## 1. はじめに

　Dockerコンテナからボリュームマウントを通じてファイルを書き出す場合、ユーザID/グループID（以下、UID/GID）を指定しないとファイルのオーナが`root`（UID`0`）になってしまい、困ることがあります。
`docker container run`コマンドの`--user`オプションでUID/GIDを指定することができますが、Linux版DockerとDocker Desktop for Macでは挙動に差があります。
これらの挙動について自分なりに整理するために、いろいろ実験しました。
なお、`Dockerfile`の`USER`命令を使用してUID/GIDを指定する方法もありますが、今回はそちらについては触れていませんのでご了承ください。

　余裕があれば、Rootlessモード編、userns-remapモード編も書きたいと思います。というか、そちらが本命なのですが。

## 2. Linux版 通常モード

　まずは、Linux版Dockerの通常モード（Rootlessモードでも、userns-remapモードでもない、一般的なモード）での挙動を調査しました。

### 2.1. 環境

　調査した環境は以下の通りです。

* ハードウェア: Raspberry Pi 4 2GB版
* OS: Ubuntu Server 20.04 LTS ARM64（64ビット）版
* Docker Engine: 19.03.8

```text
ubuntu$ cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

ubuntu$ uname -a
Linux ubuntu 5.4.0-1008-raspi #8-Ubuntu SMP Wed Apr 8 11:13:06 UTC 2020 aarch64 aarch64 aarch64 GNU/Linux

ubuntu$ sudo docker version
Client:
 Version:           19.03.8
 API version:       1.40
 Go version:        go1.13.8
 Git commit:        afacb8b7f0
 Built:             Wed Mar 11 23:43:15 2020
 OS/Arch:           linux/arm64
 Experimental:      false

Server:
 Engine:
  Version:          19.03.8
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.8
  Git commit:       afacb8b7f0
  Built:            Wed Mar 11 22:48:33 2020
  OS/Arch:          linux/arm64
  Experimental:     false
 containerd:
  Version:          1.3.3-0ubuntu2
  GitCommit:
 runc:
  Version:          spec: 1.0.1-dev
  GitCommit:
 docker-init:
  Version:          0.18.0
  GitCommit:

ubuntu$ sudo docker info
...
 Security Options:
  apparmor
  seccomp
   Profile: default
...
```

　なお、以下のコマンドは標準の`ubuntu`ユーザ（UID`1000`、GID`1000`）で実行しています。
`ubuntu`ユーザは`docker`グループには意図的に入れていないため、`docker`コマンドの実行には`sudo`を利用しています。
Rootlessモード、userns-remapモードは使用していませんが、念のため`/etc/subuid`、`/etc/subgid`の内容も示します。

```text
ubuntu$ id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),115(netdev),118(lxd)

ubuntu$ cat /etc/subuid
ubuntu:100000:65536

ubuntu$ cat /etc/subgid
ubuntu:100000:65536
```

### 2.2. 実験

　実験は以下の通りです。インラインで説明を記載しています。
なお、ファイルの読み込み権限を分かりやすくするために`umask`でパーミッションのマスクを設定しています。
また、今回の実験ではUIDとGIDに同一の値を設定していますが、もちろん別々の値でも構いません。

```text
# 実験用のディレクトリを作成します。（他ユーザでも書き込める必要があります）
ubuntu$ mkdir -p -m 777 /tmp/docker/normal

# UID/GIDを指定せず、idコマンドを実行してUID/GIDを確認します。
# →UID/GIDは0:0です。
ubuntu$ sudo docker container run --tty --rm ubuntu:20.04 id
uid=0(root) gid=0(root) groups=0(root)

# UID/GIDを指定せず、touchコマンドで空ファイルを生成して、ファイルのUID/GIDを確認します。
# →ファイルのUID/GIDは0:0です。
ubuntu$ sudo docker container run --tty --rm --volume /tmp/docker/normal:/out ubuntu:20.04 /bin/bash -c "umask 0077 && touch /out/without_user && ls -ln /out/without_user"
-rw------- 1 0 0 0 May 18 15:53 /out/without_user

# UID/GIDとして1000:1000を指定し、idコマンドを実行してUID/GIDを確認します。
# →UID/GIDは1000:1000です。
ubuntu$ sudo docker container run --tty --user 1000:1000 ubuntu:20.04 id
uid=1000 gid=1000 groups=1000

# UID/GIDとして1000:1000を指定し、touchコマンドで空ファイルを生成して、ファイルのUID/GIDを確認します。
# →ファイルのUID/GIDは1000:1000です。
ubuntu$ sudo docker container run --tty --user 1000:1000 --volume /tmp/docker/normal:/out ubuntu:20.04 /bin/bash -c "umask 0077 && touch /out/with_user_1000 && ls -ln /out/with_user_1000"
-rw------- 1 1000 1000 0 May 18 15:54 /out/with_user_1000

# UID/GIDとして2000:2000を指定し、idコマンドを実行してUID/GIDを確認します。
# →UIG/GIDは2000:2000です。
ubuntu$ sudo docker container run --tty --user 2000:2000 ubuntu:20.04 id
uid=2000 gid=2000 groups=2000

# UID/GIDとして2000:2000を指定し、touchコマンドで空ファイルを生成して、ファイルのUID/GIDを確認します。
# →ファイルのUID/GIDは2000:2000です。
ubuntu$ sudo docker container run --tty --user 2000:2000 --volume /tmp/docker/normal:/out ubuntu:20.04 /bin/bash -c "umask 0077 && touch /out/with_user_2000 && ls -ln /out/with_user_2000"
-rw------- 1 2000 2000 0 May 18 15:54 /out/with_user_2000

# Dockerホスト側のファイルのUID/GIDを確認します。
# →UID/GIDはDockerコンテナ内と同一です。
ubuntu$ ls -ln /tmp/docker/normal/
total 0
-rw------- 1    0    0 0 May 18 15:53 without_user
-rw------- 1 1000 1000 0 May 18 15:54 with_user_1000
-rw------- 1 2000 2000 0 May 18 15:54 with_user_2000

# Dockerホスト側でファイルを読めるかどうか確認します。
# →ubuntuユーザでは0:0のファイルを読むことができません。
ubuntu$ cat /tmp/docker/normal/without_user
cat: /tmp/docker/normal/without_user: Permission denied

# →ubuntuユーザでは1000:1000のファイルを読むことができます。
ubuntu$ cat /tmp/docker/normal/with_user_1000

# →ubuntuユーザでは2000:2000のファイルを読むことができません。
ubuntu$ cat /tmp/docker/normal/with_user_2000
cat: /tmp/docker/normal/with_user_2000: Permission denied
```

　Linux版Dockerの通常モードでは、割と素直な結果になりました。整理すると以下の通りです。

* `--user`オプションを指定しない場合:
    * Dockerコンテナ内での実行UID/GIDは`0:0`となり、ファイルのUID/GIDも同一となる。
    * また、Dockerホスト側から見てもファイルのUID/GIDはDockerコンテナ内と同一となる。
* `--user`オプションを指定した場合:
    * Dockerコンテナ内での実行UID/GIDは`--user`オプションで指定した値となり、ファイルのUID/GIDも同一となる。
    * また、Dockerホスト側から見てもファイルのUID/GIDはDockerコンテナ内と同一となる。

## 3. Docker Desktop for Mac

　続いてmacOS上で動作する「Docker Desktop for Mac」での挙動を調査しました。

### 3.1. 環境

　調査した環境は以下の通りです。

* ハードウェア: MacBook Pro 2018
* OS: macOS 10.14.6（Mojave）
* Docker Desktop for Mac: 2.3.0.2
* Docker Engine: 19.03.8

```text
mac$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.14.6
BuildVersion:	18G103

mac$ docker version
Client: Docker Engine - Community
 Version:           19.03.8
 API version:       1.40
 Go version:        go1.12.17
 Git commit:        afacb8b
 Built:             Wed Mar 11 01:21:11 2020
 OS/Arch:           darwin/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.8
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.17
  Git commit:       afacb8b
  Built:            Wed Mar 11 01:29:16 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.2.13
  GitCommit:        7ad184331fa3e55e52b890ea95e65ba581ae3429
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

mac$ docker info
...
 Security Options:
  seccomp
   Profile: default
...
```

　なお、以下のコマンドは作業用のユーザ（UID`501`、GID`20`）で実行しています。

```text
mac$ id -u
501

mac$ id -g
20
```

### 3.2. 実験

　実験は以下の通りです。インラインで説明を記載しています。

```text
# 実験用のディレクトリを作成します。（他ユーザでも書き込める必要があります）
mac$ mkdir -p -m 777 /tmp/docker/mac

# UID/GIDを指定せず、idコマンドを実行してUID/GIDを確認します。
# →UID/GIDは0:0です。
mac$ docker container run --tty --rm ubuntu:20.04 id
uid=0(root) gid=0(root) groups=0(root)

# UID/GIDを指定せず、touchコマンドで空ファイルを生成して、ファイルのUID/GIDを確認します。
# →ファイルのUID/GIDは0:0です。
mac$ docker container run --tty --rm --volume /tmp/docker/mac:/out ubuntu:20.04 /bin/bash -c "umask 0077 && touch /out/without_user && ls -ln /out/without_user"
-rw------- 1 0 0 0 May 18 15:23 /out/without_user

# UID/GIDとして1000:1000を指定し、idコマンドを実行してUID/GIDを確認します。
# →UID/GIDは1000:1000です。
mac$ docker container run --tty --user 1000:1000 ubuntu:20.04 id
uid=1000 gid=1000 groups=1000

# UID/GIDとして1000:1000を指定し、touchコマンドで空ファイルを生成して、ファイルのUID/GIDを確認します。
# →ファイルのUID/GIDは1000:1000です。
mac$ docker container run --tty --user 1000:1000 --volume /tmp/docker/mac:/out ubuntu:20.04 /bin/bash -c "umask 0077 && touch /out/with_user_1000 && ls -ln /out/with_user_1000"
-rw------- 1 1000 1000 0 May 18 15:24 /out/with_user_1000

# UID/GIDとして2000:2000を指定し、idコマンドを実行してUID/GIDを確認します。
# →UIG/GIDは2000:2000です。
mac$ docker container run --tty --user 2000:2000 ubuntu:20.04 id
uid=2000 gid=2000 groups=2000

# UID/GIDとして2000:2000を指定し、touchコマンドで空ファイルを生成して、ファイルのUID/GIDを確認します。
# →ファイルのUID/GIDは2000:2000です。
mac$ docker container run --tty --user 2000:2000 --volume /tmp/docker/mac:/out ubuntu:20.04 /bin/bash -c "umask 0077 && touch /out/with_user_2000 && ls -ln /out/with_user_2000"
-rw------- 1 2000 2000 0 May 18 15:24 /out/with_user_2000

# Dockerホスト側のファイルのUID/GIDを確認します。
# →UID/GIDはDockerコンテナ内と異なり、UIDはすべてmacOS側の実行ユーザ、GIDはすべて0です。
mac$ ls -ln /tmp/docker/mac/
total 0
-rw-------  1 501  0  0 May 19 00:24 with_user_1000
-rw-------  1 501  0  0 May 19 00:24 with_user_2000
-rw-------  1 501  0  0 May 19 00:23 without_user

# Dockerホスト側でファイルを読めるかどうか確認します。
# →UIDが同一のため、すべてのファイルはエラーなく読むことができます。
mac$ cat /tmp/docker/mac/without_user
mac$ cat /tmp/docker/mac/with_user_1000
mac$ cat /tmp/docker/mac/with_user_2000
```

　Docker Desktop for Macでは、Linux版Dockerの通常モードとは異なる結果になりました。整理すると以下の通りです。

* `--user`オプションを指定しない場合:
    * Dockerコンテナ内での実行UID/GIDは`0:0`となり、ファイルのUID/GIDも同一となる。（Linux版通常モードと同一の挙動）
    * ただし、Dockerホスト側から見たファイルのUID/GIDは`macOS側のUID:0`となる。（Linux版通常モードと**異なる**挙動）
* `--user`オプションを指定した場合:
    * Dockerコンテナ内での実行UID/GIDは指定した値となり、ファイルのUID/GIDも同一となる。（Linux版通常モードと同一の挙動）
    * ただし、Dockerホスト側から見たファイルのUID/GIDは`macOS側のUID:0`となる。（Linux版通常モードと**異なる**挙動）

## 4. 最後に

　本命はRootlessモード、userns-remapモードの挙動の調査なので、頑張って続きを書きたいと思います。

## 参考

* [Raspberry Pi 4にUbuntu 20.04 LTSとDockerをインストールする - Qiita](https://qiita.com/yuyakato/items/ff7b23f9cee42c937ba9)
* [docker volume mount時のuser owner問題 - Qiita](https://qiita.com/hikaruna/items/e04371fd49da0338ad81)