権限に悩まされない自作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をコンテナ作成時に自動的に渡せないため、一般ユーザを全て削除することにしました。

Dockerfile
# 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の違いを吸収できるメリットがあります。

compose.yaml
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です。

参考サイト


  1. Ubuntuの場合、一般ユーザにUIDとGIDの1000から60000が割り当てられることになっており、これらがパッケージのインストールに使用されることはありません。パッケージのインストールに使用されるIDは、0-999 と 65534 (nobody)です。 ↩︎