権限に悩まされない自作Dev Container
公開されたVSCodeのDev Containerなら問題はないが、コンテナを自作するとIDのミスマッチが起こり/workspace
の読み書きが出来ない。これはローカルと同じIDのユーザがすでにbase imageにいるため。そこで、一般ユーザを全て削除することで解決した。
またイメージを作成するときに指定したコンテナ内ユーザIDがローカルIDと異なっていると、パーミッションの問題は起きないが、イメージが肥大化する。そこでDocker や VSCode + Remote-Container のパーミッション問題に立ち向かうを参考にcompose.yaml
を使うことで、動的にコンテナ内ユーザIDを上書きしてローカルと同じIDでイメージを作成できるようにした。
問題点
Dev Containerを自分で作るとコンテナ内のUIDとローカルのUIDが一致しない。そのため権限の問題が発生して/workspace
を読み書きできない。
原因
Dev Containerは、コンテナ内で使用するIDをうまい具合にローカルのIDと一致するように書き換えてくれます。しかし自作Dev Containerだと、この書換がうまく行きません。そのため権限がないためコンテナ内から/workspace
を読み書きてきない問題が起きています。
いろいろなIDで試してみたところ、コンテナ内で使用するIDがローカルと一致していると問題は起きません。しかしコンテナ内にすでにローカルと同じIDが使われていると、IDの書き換えが起こらず問題が発生することがわかりました。
ローカルID | コンテナのID | 結果 | メモ |
---|---|---|---|
1000 | 1000 | OK | |
1000 | 1234 | ERROR | コンテナ内にID 1000がある |
1000 | 1234 | OK | コンテナ内にID 1000がない |
普通にUbuntuをインストールすると、最初のユーザはUID 1000 (GID 1000)が割り当てられます。そして、base imageのUbuntuは、ubuntuというユーザが同じUID 1000 (GID 1000)ですでに存在しています。そのためコンテ内で実行するUSER
のIDとの書き換えがうまく行かずに/workspace
の読み書きで問題が起きるということです。
解決方法
base imageの一般ユーザを削除
そこでコンテナ内の一般ユーザを全て削除してしまうことにしました1。ローカルのID同じUID (GID)のみ削除することも考えましたが、minimalなシンプル設定ではローカルのIDをコンテナ作成時に自動的に渡せないため、一般ユーザを全て削除することにしました。
# Setup user and group id
# Remove users and create a new user for USER before installing packages
RUN set -eux; \
for i in $(awk -F: '$3 >= 1000 && $3 <= 60000 { print $1 }' /etc/passwd); do \
userdel $i; \
done; \
for i in $(awk -F: '$3 >= 1000 && $3 <= 60000 { print $1 }' /etc/passwd); do \
groupdel $i; \
done; \
groupadd --gid "${GID}" "${USERNAME}"; \
useradd --uid "${UID}" --gid "${GID}" -s /bin/bash -m "${USERNAME}"
この解決策を反映させた.devcontainer
の例は、GitHubで公開しているbasic.devcontainerです。
コンテナのイメージサイズの肥大化
Dev Containerは、コンテナ内のUSER
で設定されたIDがローカルのIDと異なっていても、コンテナ内のIDがローカルIDに変更されるため/workspace
のパーミッション問題は起きません。しかし、コンテナ内のUSER
で設定されたIDがローカルのIDと異なっている場合には、コンテナイメージの肥大化とイメージ作成に時間がかかる問題が発生します。これはPythonやJavaScriptのライブラリをユーザ権限でインストールする場合に顕著です。
この問題は、Docker や VSCode + Remote-Container のパーミッション問題に立ち向かうを参考に解消しました。
compose.yaml
は.env
から環境変数を読み込めるので、compose.yaml
を使って最初のイメージを作成するときにローカルのIDでコンテナ内のUSER
で指定するユーザのIDを上書き出来ます。こうすることでIDを一致させることが出来、無駄なイメージの肥大化を抑制できます。もちろんDockerfile
を直接書き換えても同じですが、この方法ならば.env
以外はGitの管理下に置いた上でユーザIDの違いを吸収できるメリットがあります。
services:
app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile
args:
UID: ${UID:-1000}
GID: ${GID:-1000}
command: sleep infinity
volumes:
- ..:/workspaces
もっとも大体の場合のIDは1000なので、.env
を忘れてもうまく行くようにデフォルトのIDを1000にしています。
この方法を反映させた.devcontainer
の例は、GitHubで公開している.devcontainerです。
参考サイト
-
Ubuntuの場合、一般ユーザにUIDとGIDの1000から60000が割り当てられることになっており、これらがパッケージのインストールに使用されることはありません。パッケージのインストールに使用されるIDは、0-999 と 65534 (nobody)です。 ↩︎