Open5

uv in Dev Containers

fjktkmfjktkm

uv には Docker イメージが用意されているのでこれを使えばいい.ベースイメージに使うタグは debian が無難だと思うけど,Python の初期のバージョンが 3.11 なので,最新の Python のバージョンを使うならそういうやつを明示的に選んだほうが uv sync のときのダウンロード時間が減ってほんのすこし開発者体験がいいかも.
https://docs.astral.sh/uv/guides/integration/docker/

fjktkmfjktkm

以下のような decvontainer.json をベースに運用している.

{
	"name": "uv",
	"image": "ghcr.io/astral-sh/uv:debian",
	"features": {
		"ghcr.io/devcontainers/features/common-utils:2": {}
	},
	"mounts": [
		{
			"source": "uv-cache",
			"target": "/root/.cache/uv",
			"type": "volume"
		},
		{
			"target": "${containerWorkspaceFolder}/.venv",
			"type": "volume"
		},
	],
	"containerEnv": {
		"UV_LINK_MODE": "copy"
	},
	"postCreateCommand": "uv sync",
	"customizations": {
		"vscode": {
			"extensions": [
				"charliermarsh.ruff",
				"ms-python.python",
				"tamasfe.even-better-toml"
			]
		}
	}
}

featurescommon-utils は入れるとターミナルの見た目が Microsoft 謹製の Dev Container 用イメージと同じ感じになるので追加している.不要だと思う人は削除してもろて.

https://github.com/devcontainers/features/pkgs/container/features%2Fcommon-utils

mounts については,uv のキャッシュを名前付きボリュームでマウントしている.これにより,ほかのプロジェクトなどともキャッシュを共有できる..venv は名前なしのボリュームマウントにしている.バインドバウントではないのでクラウドストレージと相性が良いし,リビルドするたびに作り直しになるがキャッシュがあるのでほとんど時間はかからない.

containerEnv については,UV_LINK_MODEcopy に指定している.これは,uv のキャッシュをボリュームマウントにすると,ボリュームが異なるため uv のデフォルトの hardlink モードが利用できないためである.別に設定しなくても使えるけどいちいち警告が出るのもうざいので設定してある.pyproject.toml でも同様の設定ができるが,Dev Container を使わずローカルで開発する場合にも影響してしまうので Dev Container の環境変数で指定する方法を採用している.

postCreateCommand については,uv sync のみでシンプルに運用できる.これはリビルドするたびに venv を作り直す運用にしたから.もしvenv をバインドマウントにしたり名前を付けて永続ボリュームにしたら,ここでいろいろごにょごにょ書かないといけなくなる.

customizations はお好きなように.

fjktkmfjktkm

Dev Container によるコンテナ仮想化と,venv による Python 実行環境の仮想化は全く別のレイヤーなので,Dev Container を使っているから venv 不要みたいな考えはやめたほうがいいと思う.パッケージは更新頻度が高いので,開発コンテナの Dockerfile にパッケージのインストールの処理を含めるのはナンセンス.

fjktkmfjktkm

非 root ユーザーで実行するのは,マウントの設定がある場合むずかしい.

		{
			"source": "uv-cache",
			"target": "/root/.cache/uv",
			"type": "volume"
		},

このマウントは root ユーザーで実行されるので,非 root ユーザーが uv sync を実行すると Permission Dinied になってしまう.