いまさらdevcontainer入門

昔作ったPerl/Pythonで書いたやつをコンテナ詰め詰めした際にdevcontainer使ってみました。FastCGIモジュールとかWindows環境でうまく動かせなかったので便利ですねこれ。

お仕事などではGoを書くことが多く、コンテナに詰めなくても開発マシン(WindowsMac)とサーバホスト(Linux)でビルドすれば動くのでこの辺の知見は一から集める。

tech.pepabo.com

この記事見て「devcontainer簡単そう」って思って使ってみたらpostCreateCommanでのchmod +xが必要になって、なんで要るんやろとか調べてたら沼にハマっていった(これについては最後に述べる)。

なお、わたしWindowsユーザなのでMacとかLinuxでどうなのかは謎1。以下書かれていることはすべてWindows 10 Pro(22H2)でのお話です。

たどり着いた答え

色々試行錯誤した結果、「python:3.11.0-slim-busterをベースにしてfeaturesでcommon-utilsを入れる。」に収束しました。イメージサイズは334MBになります。

{
    "image":"python:3.11.0-slim-buster",
    "features": {
        "ghcr.io/devcontainers/features/common-utils:2": {
            "username":"vscode"
        }
    }
    "containerUser":"vscode"
}

github.com

これから話す話は基本的にPythonでの話です。Perlはprebuilt imageがないので、Alpineイメージに色々入れました。

github.com

試行錯誤の記録

github.com

だいたいここにあります。

script-libraryをpython:3.11.0-slim-busterに入れる

github.com

イメージサイズは375MBでした。

FROM python:3.11.0-slim-buster

RUN bash -c "$(curl -fsSL "https://raw.githubusercontent.com/microsoft/vscode-dev-containers/main/script-library/common-debian.sh")" \
    && apt-get clean -y && rm -rf /var/lib/apt/lists/*

USER vscode

最終行で「USER vscode」とするのは、contaienrUservscode指定してるのと同じっぽい。

USERremoteUsercontainerUser

DockerfileではUSER指定できるし、devcontainer.jsonにはremoteUsercontainerUserというkeyがあるけど、どう違うのか。

devcontainer.jsonschemaから説明を取ってきた。

{
    "remoteUser": {
        "type": "string",
        "description": "The username to use for spawning processes in the container including lifecycle scripts and any remote editor/IDE server process. The default is the same user as the container."
    },
    "containerUser": {
        "type": "string",
        "description": "The user the container will be started with. The default is the user on the Docker image."
    }
}

remoteUserは「The username to use for spawning processes in the container including lifecycle scripts and any remote editor/IDE server process.」で、containerUserは「The user the container will be started with. The default is the user on the Docker image.」とある。

remoteUserはコンテナでLifecycle scripts(後述)やIDE(VSCode)を起動する時のユーザで、containerUserはコンテナ起動時のユーザと書いてある。おそらく後者はコンテナENTRYPOINT起動するユーザなんだろなとdocker exec -it (コンテナID) shで実行中のdevcontainer乗り込んだらcontainerUserでshが上がってきたので、たぶんそう。

code.visualstudio.com

とりあえずcontainerUserだけ指定しておけば良さそうな気がするけど、MSのドキュメント見るとremoteUserを主に使うような雰囲気を感じる。

ちなみに、Lifecycle scripts(postCreateCommandなど)が上記の説明通り指定されたユーザで動く一方で、Docker Desktop for Windows等は走らせるスクリプトrootでmountする(ことになっている)ので、sudo chmod2としておかないと失敗することがあります(後述)。

prebuilt image

imageで参照するだけ。イメージ1.44Gもあってびっくりしたので撤退。ところでmcr.microsoft.comってタグ一覧わかりにくすぎてつらいですね3

github.com

base imageにpython featureを入れる

github.com

base image(mcr.microsoft.com/devcontainers/base:debianあたり)の上にfeaturesでPython入れると、Pythonソースコードからビルドするので初回構築がなかなかに時間を食う上に1G越え。よく見るとmcr.microsoft.com/devcontainers/base:debianだけで810MBある。

devcontainerで起動したときに fatal: unsafe repositoryって言われる

issueが立ってる。

github.com

git config --global --add safe.directory ${containerWorkspaceFolder}をする必要があるとな。素朴に考えるとコンテナ生成時4のlifecycle scriptsで叩くんですが、コンテナ内に.gitconfigが既に存在する状態で起動するとVSCode.gitconfigを転送しないらしくuser.emailなどが未設定となってgit操作ができなくなる。

The extension will automatically copy your local .gitconfig file into the container on startup so you should not need to do this in the container itself.

Sharing Git credentials with your container

ドキュメントにはそんな事書いてないぞ~のissue。 github.com

なので、コンテナ生成時でなく起動時のlifecycle scriptでgit configを叩けば良さそう。

{
   "postCreateCommand": "git config --system --add safe.directory ${containerWorkspaceFolder}"
}

Lifecycle Scriptsについて

Lifecycle scriptsの説明を見てると、コンテナ生成時にonCreateCommand/updateContentCommand/postCreateCommandの3つが順次コンテナ内で走ると書いてある。で、waitForの説明で「デフォだとupdateContentCommand終わったらツール(VSCode IDEとかのことだと思う)接続するよ」とか書いてあるんだけどそれ以外の違いがようわからん。

で、ぐぐるGitHub Codespacesの説明がヒットする。

onCreateCommand はプレビルドが作成されるときに 1 回だけ実行されますが、updateContentCommand はテンプレートの作成時とそれ以降の更新時に実行されます。 インクリメンタル ビルドは updateContentCommand に含める必要があります。これらはプロジェクトのソースを表し、プレビルドの更新ごとに含める必要があるためです。

プレビルドに含める時間のかかるタスクの構成

GitHub Codespaceで当該リポジトリいじるとして、最初にGitHub Codespaceを有効化したときにGitHub上でonCreateCommandが走って、そのリポジトリが更新される(などの)際にupdateContentCommandが走ってCodespaces用のコンテナイメージをよしなにするみたい。で、GitHub Codespaceで個々人の環境(かつ認証情報)で起動するものはpostCreateCommandを使え、ということのよう。使ったことないんでわかんないんですが・・・。

ローカルで使うぶんには気にしなくて良さそうですが、onCreateCommandはlinterみたいなのを入れるときに使ってupdateContentCommandは依存ライブラリ入れるのに使うと良いのかなぁとかそんな雰囲気。

Lifecycle Scriptsでのexecutable permission設定が必要なのは何故だったのか。

Docker Desktop for Windows のドキュメントにはこう書いてあるので本当はchmod +xいらないはず。

When sharing files from Windows, Docker Desktop sets permissions on shared volumes to a default value of 0777 (read, write, execute permissions for user and for group). https://docs.docker.com/desktop/troubleshoot/topics/#volumes

でも実際にいじってみるとDocker Desktop (Windows)がファイルのexecutable permissionを何らかの形で覚えているような挙動をする5。なんかがおかしい。変だな~と思ってぐぐるとissue見つけた。

github.com

書いてある通り、fileのownerがrootでなくvscodeなどになったりするのも生じている6

file ownerの話はさておき、file permissionが覚えてるの謎(たぶんfile ownerも覚えてそう)ですが現状を観測してみます7

PS C:\Users\walkure> docker run --rm -it --mount type=bind,source=C:\Users\walkure\test,target=/poyo alpine sh
/ # ls -l /poyo
total 0
-rwxrwxrwx    1 root     root             0 Jan 14 10:55 a.txt <--- 最初は +x されてた
/ # chmod -x /poyo/a.txt                                       <--- -x した
/ # ls -l /poyo
total 0
-rw-rw-rw-    1 root     root             0 Jan 14 10:55 a.txt  <--- -x が反映
/ # exit

PS C:\Users\walkure\test> docker run --rm -it --mount type=bind,source=C:\Users\walkure\test,target=/fuga alpine sh
/ # ls -l /fuga/
total 0
-rw-rw-rw-    1 root     root             0 Jan 14 10:55 a.txt  <--- -x が保存されてる
/ # chmod +x /fuga/a.txt                                        <--- +x した
/ # exit

PS C:\Users\walkure> docker run --rm -it --mount type=bind,source=C:\Users\walkure\test,target=/poyo alpine ls -l /poyo
total 0
-rwxrwxrwx    1 root     root             0 Jan 14 10:55 a.txt <--- +x が反映

Rancher Desktop(Windows)でdevcontainerが上がってこない

Rancher Desktopのdocker-buildx.exeがDocker CLI拡張として認識される場所に入ってなくて、VSCodeでdevcontainer起動する際にdocker buildxが失敗してコケる。同様の理由でWSL上でもdocker buildxができない。

github.com

%USERPROFILE%\.docker\cli-plugins乃至C:\ProgramData\Docker\cli-pluginsとかにdocker-buildx.exeをコピってやれば良いんだけど。


  1. user idやfile owner/permissionの話など、それなりに変わると思われる。
  2. featuresやscript libraryでvscodeユーザを生成するとsudoersに追加してくれる。
  3. Microsoft Artifact Registry (mcr.microsoft.com) にあるコンテナーイメージのタグ一覧を確認する - Qiita
  4. onCreateCommand/updateContentCommand/postCreateCommand のみっつ。
  5. Dockerデーモン再起動しても変わらないのでどこかに永続化されてそうだけど、よくわからない・・・。ファイルはNTFS上にいるので、どうなっているのか。
  6. rootでmountされているならばvscodeユーザでchmodしたら怒られるのが正しいのに、chmodsudoなくても動く挙動を観測していた。
  7. Docker Desktop 4.16.1 (95567)およびRancher Desktop v1.7.0でやってみました