はじめに
私は現在Azure上で実行される生成AIアプリケーションの開発を行っていますが、 App Service・Functions
へデプロイを行うにはコンテナを利用しないととてもじゃないがやってられないと感じています。
バックエンド側には生成AIや他ML等と相性が良いPythonを利用しているのですが、最近流行りのモジュール等管理ツール uv
をコンテナで利用したい!動作速いし、依存関係も勝手に記載してくれる!・・・でも勝手に .venv
が作成されるのが嫌だなと思った次第です。
そもそもが仮想環境であるDockerコンテナに venv
仮想環境をさらに作成する意味もありませんし、余計な管理コストや技術負債は増やしたくないものです。
作りたい環境のイメージ
- pythonの開発用Dockerコンテナ
-
uv
が高速かつ依存関係の自動記載等便利ため、開発・マルチステージBuilder用に使いたい .venv
を作りたくない- Docker内のシステムPythonのインストール先にモジュールをインストールしたい
- 依存関係が記載されたファイル
uv.lock
はプロジェクト内のフォルダに配置しておきたい - 拡張機能などを自動でインストールしたいため、
Devcontainer
拡張機能を利用したい
結論
下記の環境変数をコンテナに設定する。
-
UV_SYSTEM_PYTHON=1
- systemにインストールされているPythonを利用する
-
UV_PROJECT_ENVIRONMENT=/usr/local/
- 使用するPython環境を
/usr/local
に設定し、venv
環境を作成しない
- 使用するPython環境を
事前準備
- Docker開発環境(
Docker Desktop
Docker CLI
等)の構築 -
Visual Studio Code
のインストール -
Dev Containers
拡張機能のインストール
注意
- 筆者の環境は
Windows 11 24H2
なのでmacやLinuxでは多少動作が異なるかもしれません - 本記事のDockerコンテナはRootで利用しているため、本場環境では作業用ユーザーの作成など行ってください
- 本番用コンテナに
uv
がインストールされているべきではないため、本番環境の構築にはマルチステージビルドを利用してください
開発コンテナの構築
ディレクトリ構成
project-root>tree /F
project-root
│ .python-version
│ Dockerfile
│ pyproject.toml
│ README.md
│ (ruff.toml)
│ uv.lock
│
├─.devcontainer
│ devcontainer.json
│
└─src
hello.py
uv init
後の想定されるディレクトリ構成です。 ruff.toml
は pyproject.toml
と同一にして問題ありません。
Dockerfile
FROM python:3.11.9-slim-bookworm AS develop
WORKDIR /app
# uvの破壊的バージョンアップ対策として特定のバージョンを指定してインストール
COPY --from=ghcr.io/astral-sh/uv:0.5.3 /uv /uvx /bin/
ENV UV_SYSTEM_PYTHON=1 \
UV_PROJECT_ENVIRONMENT=/usr/local/
RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
apt-get update
COPY . .
# 依存関係の解決(uv init後、uv.lock生成後にコメントアウト解除)
# RUN --mount=type=cache,target=/root/.cache,sharing=locked \
# uv sync --frozen
今回ベースとなるコンテナイメージは、Debian bookworm slim
の python 3.11.9
コンテナを利用しています。 Azure App Service
・ Azure Functions
のサポートするPythonバージョンが最近まで 3.11
までだった名残です。
uvはまだ登場したばかりで、ときたま破壊的なアップデートが行われるため、現状ですとバージョンを指定してインストールしておけばチームメンバー間での差異もなくなりよさそうです。
依存関係の解消部分は、 uv.lock
ファイルが存在しないといけないため、 uv init
を行った後にコメントアウトを解除してください。
Devcontainer.json
{
"name": "My Dev Container",
"workspaceFolder": "/app",
"workspaceMount": "source=${localWorkspaceFolder},target=/app,type=bind,consistency=cached",
"build": {
"dockerfile": "../Dockerfile"
},
"settings": {},
"extensions": [
"ms-python.python",
"ruffcharliermarsh.ruff"
]
}
ホストのカレントワークスペースフォルダをコンテナ内 /app
にマウントしています。uv
を使うなら同じくRustで作られたPythonのLinter・Formatter Ruff
が便利です。
開発コンテナの起動
-
Docker Desktop
を起動します -
F1で
Devcoitainers: Reopen in Container
をクリックし、開発用コンテナを起動します -
プロジェクトの初回起動時は、uvの初期設定コマンドを実行し、
pyproject.toml
などを生成しますuser:/app# uv init
-
プロジェクトで利用するpythonモジュールをインストールします
user:/app# uv add requests Resolved 6 packages in 5ms Prepared 5 packages in 207ms Installed 5 packages in 7ms + certifi==2025.1.31 + charset-normalizer==3.4.1 + idna==3.10 + requests==2.32.3 + urllib3==2.3.0
1.uv.lockが生成されたら次回以降はDockerfileの uv sync
部分のコメントアウトを解除すれば、Dev ContainerでRebuildを行った際にも勝手に依存関係を解消してくれます。他メンバーがCloneして使う際もDockerfileで uv sync
しておけば勝手に依存関係は解消されます。
各モジュールはシステムのPython user/local/site-package
にインストールされており、 uv pip list
でも、デフォルトの pip list
でも同じ個所を参照してくれています。
user:/app# uv pip list
Using Python 3.11.9 environment at /usr/local
Package Version
------------------ ---------
certifi 2025.1.31
charset-normalizer 3.4.1
idna 3.10
pip 24.0
requests 2.32.3
setuptools 65.5.1
urllib3 2.3.0
wheel 0.44.0
user:/app# pip list
Package Version
------------------ ---------
certifi 2025.1.31
charset-normalizer 3.4.1
idna 3.10
pip 24.0
requests 2.32.3
setuptools 65.5.1
urllib3 2.3.0
wheel 0.44.0
プロジェクトのルートに .venv
が作られず、Dockerコンテナ内のシステムPythonにパッケージがインストールされるようになりました!解決!
本番用コンテナを作成するときは、Composeのマルチステージビルドを利用して AS builder
ステージでuv sync --frozen --no-dev
を行い site-packages
を AS production
にコピーすれば行けそうですね。
参考