dockerコンテナ内でホストからマウントされたファイルシステムにアクセスするときのパーミッションについて

今までdockerをなんとなくで触ってきていましたが,最近触ることがありその中で気づいたことがあるのでメモがてら書いておきます.

マウントされたファイルシステムにアクセスするときのパーミッションエラー問題

調べるといろいろ出てくると思いますが,コンテナにファイルシステムをマウント*1したとき,ホストマシンとコンテナ間でuidやgidがそのまま使われてしまうことによっていざアプリケーションを動かそうとしたりコンテナが書き込んだデータがroot所有になっていたりする,という問題がlinuxでは生じます*2

この問題に遭遇した時,様々な解決策が講じられていますが,とりあえずそれらは置いておいて,なぜこのような現象が起きるのかを理解するためにいくつか実験を行います.

実験その1 〜特に何も考えず書き込む〜

まずは特に何も考えずbind mountを行ってコンテナ内でマウントされたディレクトリに書き込みを行います.

以下のようなDockerfileを用意して

FROM debian:11-slim

CMD ["touch", "/hogehoge/test.txt"]
koutarou@koutarou-desktop/INS> docker run --rm -v $(pwd)/hogehoge:/hogehoge docker-permission-test

koutarou@koutarou-desktop/INS> ls
Dockerfile  hogehoge

koutarou@koutarou-desktop/INS> ls -l
合計 8
-rw-rw-r-- 1 koutarou koutarou   57  616 21:26 Dockerfile
drwxr-xr-x 2 root     root     4096  616 21:26 hogehoge

koutarou@koutarou-desktop/INS> ls hogehoge -l
合計 0
-rw-r--r-- 1 root root 0  616 21:26 test.txt

マウントしたhogehogeディレクトリにtest.txtというファイルを作るだけです. hogehogeディレクトリは走らせた時点で存在していないので勝手に(root:rootで)作られ,その中にはこれまたroot:rootなtest.txtができています.

それでは,予めhogehogeディレクトリがある状態で走らせるとどうでしょうか?

koutarou@koutarou-desktop/INS> mkdir hogehoge

koutarou@koutarou-desktop/INS> docker run --rm -v $(pwd)/hogehoge:/hogehoge docker-permission-test

koutarou@koutarou-desktop/INS> ls -l
合計 8
-rw-rw-r-- 1 koutarou koutarou   57  616 21:26 Dockerfile
drwxrwxr-x 2 koutarou koutarou 4096  616 21:30 hogehoge

koutarou@koutarou-desktop/INS> ls hogehoge -l
合計 0
-rw-r--r-- 1 root root 0  616 21:30 test.txt

hogehogeがroot:rootではなくmkdirしたユーザー・グループ所有のものになっています.

実験その2 〜コンテナで実行するユーザーを指定してみる〜

先程はdocker runするときに特にユーザーを意識しませんでしたが,公式ドキュメントにはコンテナ内で実行されるプロセスの実行ユーザーを指定することができると書いてあります.

先ほどと同じDockerfileでdocker runする時にユーザー指定してみるとどうなるでしょうか?

koutarou@koutarou-desktop/INS> mkdir hogehoge

koutarou@koutarou-desktop/INS> chmod 777 hogehoge

koutarou@koutarou-desktop/INS> docker run --rm -u=1001:1001 -v $(pwd)/hogehoge:/hogehoge docker-permission-test

koutarou@koutarou-desktop/INS> ls -l
合計 8
-rw-rw-r-- 1 koutarou koutarou   57  616 21:26 Dockerfile
drwxrwxrwx 2 koutarou koutarou 4096  616 21:37 hogehoge

koutarou@koutarou-desktop/INS> ls -l hogehoge
合計 0
-rw-r--r-- 1 1001 1001 0  616 21:37 test.txt

docker runするときに1001:1001というユーザー・グループで実行するように指定しました. その結果,test.txtは1001:1001所有になりました.

ドキュメントにはデフォルトではidが0のユーザー(多くの場合はroot)が使われると書いてあります.そのため実験1ではroot:root所有になったと考えられます. そして指定すると指定通りになりました. なお,私のホストマシンにはidが1001のユーザー・グループが存在していないため数字での指定となっています. これを存在する番号で指定すると下のように名前で表示してくれるようになります.

koutarou@koutarou-desktop/INS> docker run --rm -u=117:124 -v $(pwd)/hogehoge:/hogehoge docker-permission-test

koutarou@koutarou-desktop/INS> ls -l hogehoge
合計 0
-rw-r--r-- 1 pulse pulse 0  616 21:41 test.txt

ここで注意してほしいのは,コンテナには117番のユーザーや124番のグループは存在していないのにホストマシンでlsした結果適切な名前で表示されているということです. つまり,コンテナは指定されたら指定の通りの実行ユーザーでファイルシステムにアクセスを行いますが,そのアクセスはホストマシンのファイルシステムにもそのまま適用されるということです. よって,dockerはコンテナとホストマシンの間で特にユーザー・グループの変換を行わず,単純にファイルシステムに番号だけを書き残すということが分かります.

なお,ユーザーの指定はdocker runするときだけでなく,下のようにDockerfileにも書くことができます*3

FROM debian:11-slim
USER 1001:1001

CMD ["touch", "/hogehoge/test.txt"]

実験その3 〜mkdirせずにマウントしたディレクトリに対してユーザー指定したコンテナでアクセスする〜

実験2では手動でmkdirしていましたが,これをdockerに任せるとどうなるでしょうか?ユーザー指定したのでそのパーミッションで作ってくれるのでしょうか?

koutarou@koutarou-desktop/INS> docker run --rm -u=1001:1001 -v $(pwd)/hogehoge:/hogehoge docker-permission-test
touch: cannot touch '/hogehoge/test.txt': Permission denied

koutarou@koutarou-desktop/INS> ls -l
合計 8
-rw-rw-r-- 1 koutarou koutarou   57  616 21:57 Dockerfile
drwxr-xr-x 2 root     root     4096  616 21:57 hogehoge

permission deniedエラーが出てコンテナ実行はエラーに終わりました. また,hogehogeディレクトリはroot:root所有となっています.

実験2までの知見から,「1001番のユーザー」(名前を意識していないのであえてこういう表記にしています)がroot:rootで766なディレクトリに対してファイルを作ろうとしたのでエラーが出たのだ,ということが推測され,恐らく実際その通りです.

ここまでは割と自明なのですが,当初の希望的観測では,ユーザー指定したらそのパーミッションディレクトリを作ってくれるのではとしていました. どうもこれは成り立たなかったようです.

公式ドキュメントによると,マウントするディレクトリが存在しなかった場合にはその都度作られる,としています. 誰が作っているかは書いてなさそうでしたが恐らくdockerの中核であるdocker engineだと思われます. docker engine自体はrootで動いているので新しく作られたディレクトリはroot:root所有になるということです.

勝手にユーザーを推定してくれても親切じゃないかとも思いますが,よく考えてみるとdocker runで指定しないでもDockerfileに書いてイメージ自体に実行ユーザーを設定することもできますし((というかdocker runのuオプション自体それを上書きするという挙動です)),存在しない番号を指定するみたいなこともできてしまうのでdocker engineに任せるというのが自然ですね.

まとめ

  • コンテナは指定されたユーザー番号(デフォルトでは0番のroot)でコンテナ内のアプリケーションを実行する.このユーザーはコンテナ・ホスト共に存在しないものを指定してもよい.
  • コンテナ内でマウントされたホスト上のファイルシステムに書き込むとホスト上でもそのユーザー番号で書き込まれる
  • bind mountで存在しないディレクトリを指定するとdocker engineが勝手に作ってくれ,その所有者はdocker engineの実行ユーザー(多くの場合root)となる

*1:公式ドキュメント的にはbind mountというらしいです.docker管理領域の中に作ってくれるvolumeとは区別されるものです

*2:正確にはunix系コンテナとunix系ホストだと思う.windows系が絡んでくるとどうなるんだろう?

*3:

docs.docker.com