WSL2+VSCode Dev ContainerでFilesharingの警告 - Qiitaで書いた方法でDev Containerを動かすと、一部のイメージでパーミッションに問題が出た。
原因
Windowsファイルシステム上のフォルダをvolumes
でバインドマウントしていたときは何の問題もなかった。おそらく、WindowsファイルシステムにLinuxのようなパーミッションがなかったため、よしなにしていてくれたのだと思う。
WSL2を使うとLinuxファイルシステム上のディレクトリをバインドマウントするようになるため、パーミッションが問題になる。
解決方法
- コンテナ上にWSL2上のディストリビューションで使っているユーザーと同じUID/GIDのユーザーがあれば、保存されるファイルのパーミッションも同じになるのでそれを使う。
- 該当ユーザーがなかったら、Dockerfileなどでユーザーを作る
まずはWSL2のディストリビューション(今回はUbuntu)でUID/GIDを確認しておく。
$ id -u
1000
$ id -g
1000
一般ユーザーが用意されていないイメージ(alpineなど)
もしイメージ内に一般ユーザーが用意されていない(rootしかない)場合は、Dockerfileやスクリプトでユーザーを追加することになる。VSCodeの Advanced Container Configuration のページにやり方が書いてあった。
ただし、上記リンクのサンプルはUIDが決め打ちで、汎用性が低い。Dev Containerとしてプロジェクトに設定をコミットし、チームメンバーと環境を共有することまで想定すると、用意するユーザー名/UID/GIDは各自のマシンに合わせた値になってほしい。
devcontainer.json
にはinitializeCommand
で「イメージがビルドされる前に実行するコマンド」を指定できる。ここでユーザーのUID/GIDを調べて環境変数ファイル.env
に保存すれば、docker-compose.yml
を経由してDockerfileやコンテナに渡すことができる。
ソースコード例
# .env ファイルを作る
echo "UID=$(id -u $USER)" > .env
echo "GID=$(id -g $USER)" >> .env
echo "USERNAME=$USER" >> .env
{
"initializeCommand": "${localWorkspaceFolder}/.devcontainer/init.sh",
}
version: '3'
alpine:
container_name: wsl_example_alpine
build:
context: ./docker/alpine/
args: # ← Dockerfileに渡すにはARGを使う
USERNAME: $USERNAME
UID: $UID
GID: $GID
tty: true
volumes:
- ..:/workspace
FROM alpine:3
RUN apk add --no-cache sudo
ARG USERNAME
ARG UID
ARG GID
RUN apk add --no-cache shadow \
&& groupadd --gid $GID $USERNAME \
&& useradd --uid $UID --gid $GID -m $USERNAME \
&& echo "$USERNAME ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$USERNAME \
&& chmod 0440 /etc/sudoers.d/$USERNAME \
&& apk del shadow
USER $USERNAME
一般ユーザーが用意されているイメージ(node-alpineなど)
今回使用したnode:14-alpine
では、デフォルトでnode
というnon-rootなユーザーが存在していた。そのユーザーを使う設定をする。
devcontainer.json
のremoteUser
で、VSCodeからコンテナへの様々な操作(統合ターミナルを開く、デバッガーを動かす、など)で使用されるユーザーを指定できる。
{
"remoteUser": "node",
}
しかし、使いたいユーザーが1000:1000
でないときは困る。前項のようにユーザーを作る処理を入れると今度は逆に、イメージにすでに該当ユーザー名やUIDのユーザーが存在する場合に困る。
これについても先ほどのドキュメント Advanced Container Configuration に方法があった。
イメージ内にユーザーが存在していることはわかっている前提で、そのユーザーのUID/GIDを書き換えてしまおうというものだ。この場合、UID/GIDが元から一致していたとしてもエラーにならない。
ソースコード例
-
init.sh
とjson:devcontainer.json
のinitializeCommand
は同じなので省略
version: '3'
nodealpine:
container_name: wsl_example_nodealpine
build:
context: ./docker/nodealpine/
args:
UID: $UID
GID: $GID
tty: true
volumes:
- ..:/workspace
FROM node:14-alpine
RUN apk add --no-cache sudo
ARG UID
ARG GID
RUN apk add --no-cache shadow \
&& groupmod --gid $GID node \
&& usermod --uid $UID --gid $GID node \
&& chown -R $UID:$GID /home/node \
&& apk del shadow
番外編:postgres公式イメージ
READMEの「Arbitrary --user
Notes」セクションにあるが、docker run
の--user
オプションで与えられたユーザーでpostgresを実行できるらしい。
ここに.env
で設定した$UID:$GID
を与えてやれば、データディレクトリの中もユーザーのパーミッションにでき、開発環境では便利そうだ。docker-compose
の場合はuser
オプション。
ソースコード例
-
init.sh
とjson:devcontainer.json
のinitializeCommand
は同じなので省略
version: '3'
services:
postgres:
container_name: wsl_example_postgres
user: $UID:$GID # ←これ
build: ./docker/postgres/
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: tekitou-na-password
volumes:
- ./docker/postgres/data:/var/lib/postgresql/data
まとめ
- UIDやGIDを含んだ
.env
ファイルを作るためのスクリプトを作成する -
devcontainer.json
のinitializeCommand
で1のスクリプトを実行し.env
ファイルを作る - その値を
docker-compose.yml
からARG
を使ってDockerfileに渡す - 使用するイメージに合わせてDockerfileや設定ファイルを編集し、ユーザーを操作
- non-rootユーザーがないイメージの場合、ユーザーを追加し、Dockerfileの最後に
USER
でデフォルトユーザーとして設定(alpineサンプル) - non-rootユーザーがあるイメージの場合、そのユーザーのUID/GIDのみを変更し、
devcontainer.json
のremoteUser
に指定(node-alpineサンプル) - その他、イメージに応じて設定可能(postgresサンプル)
ソースコード全文: noonworks/vscode-dev-container-example