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

お仕事などではGoを書くことが多く、コンテナに詰めなくても開発マシン(WindowsやMac)とサーバホスト(Linux)でビルドすれば動くのでこの辺の知見は一から集める。
この記事見て「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"
}
これから話す話は基本的にPythonでの話です。Perlはprebuilt imageがないので、Alpineイメージに色々入れました。
試行錯誤の記録
だいたいここにあります。
script-libraryをpython:3.11.0-slim-busterに入れる
イメージサイズは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」とするのは、contaienrUserでvscode指定してるのと同じっぽい。
USERとremoteUserとcontainerUser
DockerfileではUSER指定できるし、devcontainer.jsonにはremoteUserとcontainerUserというkeyがあるけど、どう違うのか。
devcontainer.jsonのschemaから説明を取ってきた。
{
"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が上がってきたので、たぶんそう。
とりあえずcontainerUserだけ指定しておけば良さそうな気がするけど、MSのドキュメント見るとremoteUserを主に使うような雰囲気を感じる。
ちなみに、Lifecycle scripts(postCreateCommandなど)が上記の説明通り指定されたユーザで動く一方で、Docker Desktop for Windows等は走らせるスクリプトをrootでmountする(ことになっている)ので、sudo chmod2としておかないと失敗することがあります(後述)。
prebuilt image
imageで参照するだけ。イメージ1.44Gもあってびっくりしたので撤退。ところでmcr.microsoft.comってタグ一覧わかりにくすぎてつらいですね3。
base imageにpython featureを入れる
base image(mcr.microsoft.com/devcontainers/base:debianあたり)の上にfeaturesでPython入れると、Pythonをソースコードからビルドするので初回構築がなかなかに時間を食う上に1G越え。よく見るとmcr.microsoft.com/devcontainers/base:debianだけで810MBある。

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

issueが立ってる。
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.
ドキュメントにはそんな事書いてないぞ~の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見つけた。
書いてある通り、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ができない。
%USERPROFILE%\.docker\cli-plugins乃至C:\ProgramData\Docker\cli-pluginsとかにdocker-buildx.exeをコピってやれば良いんだけど。
- user idやfile owner/permissionの話など、それなりに変わると思われる。↩
-
featuresやscript libraryで
vscodeユーザを生成するとsudoersに追加してくれる。↩ - Microsoft Artifact Registry (mcr.microsoft.com) にあるコンテナーイメージのタグ一覧を確認する - Qiita↩
-
onCreateCommand/updateContentCommand/postCreateCommandのみっつ。↩ - Dockerデーモン再起動しても変わらないのでどこかに永続化されてそうだけど、よくわからない・・・。ファイルはNTFS上にいるので、どうなっているのか。↩
-
rootでmountされているならばvscodeユーザでchmodしたら怒られるのが正しいのに、chmodがsudoなくても動く挙動を観測していた。↩ - Docker Desktop 4.16.1 (95567)およびRancher Desktop v1.7.0でやってみました↩