Today I learnedです。
以下の記事のアップデートとなります。
目次
uvを使ったプロジェクトを複数のPythonバージョンでDockerイメージビルドしたい
ここまででマルチステージビルドを使って、uvで環境構築したPythonライブラリのDockerイメージをビルドできるようになっています。
イチオシポイントは、uvを残さないという点です。
今回はARGを使って、uv(やpython)のイメージを切り替えられるようにしてみたいと思ったのです。
ghcr.io/astral-sh/uv:python3.11-bookwormghcr.io/astral-sh/uv:python3.12-bookwormghcr.io/astral-sh/uv:python3.13-bookworm
ARGとは
https://docs.docker.com/reference/dockerfile/#arg
docker build --build-arg <vername>=<value>でDockerfileに変数を渡せます。
DockerfileにARGを書くと
ARG awesome_value
docker build --build-arg awesome_value=HOGEで、Dockerfileの中でawesome_value(変数)はHOGEと評価されます。
例えばRUNでecho ${awesome_value}すると「HOGE」が出力されます。
実は最近の記事でも使っています。
docker build --build-arg python_version=3.10 -t ubuntu-python:3.10 .
マルチステージビルドとARG
このARGを使って最初に書いたDockerfileがこちら
ARG python_version
FROM ghcr.io/astral-sh/uv:python${python_version}-bookworm AS builder
WORKDIR /mylib
ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
COPY . /mylib/
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev --no-editable
FROM python:${python_version}-slim-bookworm
COPY --from=builder /mylib/.venv/lib/python${python_version}/site-packages /usr/local/lib/python${python_version}/site-packages
CMD ["python", "-m", "mylib"]
- 各ステージのイメージのPythonのバージョンを
ARGを使って切り替えられるようにしました - 最終イメージの
COPY命令で${python_version}はコマンドラインから指定した値に展開される(と期待)
しかしこちらは動かないのです。
% docker --version Docker version 27.2.1-rd, build cc0ee3e % docker build --no-cache --build-arg python_version=3.12 --progress plain -t uv-practice-lib:py3.12-0.4.0 . #11 [stage-1 2/2] COPY --from=builder /mylib/.venv/lib/python/site-packages /usr/local/lib/python/site-packages #11 ERROR: failed to calculate checksum of ref 39d22783-408e-49b5-8fa5-01669e631717::v5iaj17vb9y5956yf1jqmz9k0: failed to walk /var/lib/docker/tmp/buildkit-mount766007966/mylib/.venv/lib/python: lstat /var/lib/docker/tmp/buildkit-mount766007966/mylib/.venv/lib/python: no such file or directory ------ > [stage-1 2/2] COPY --from=builder /mylib/.venv/lib/python/site-packages /usr/local/lib/python/site-packages: ------ Dockerfile:11 -------------------- 9 | 10 | FROM python:${python_version}-slim-bookworm 11 | >>> COPY --from=builder /mylib/.venv/lib/python${python_version}/site-packages /usr/local/lib/python${python_version}/site-packages 12 | CMD ["python", "-m", "mylib"] 13 | -------------------- ERROR: failed to solve: failed to compute cache key: failed to calculate checksum of ref 39d22783-408e-49b5-8fa5-01669e631717::v5iaj17vb9y5956yf1jqmz9k0: failed to walk /var/lib/docker/tmp/buildkit-mount766007966/mylib/.venv/lib/python: lstat /var/lib/docker/tmp/buildkit-mount766007966/mylib/.venv/lib/python: no such file or directory
COPY命令の部分、/mylib/.venv/lib/python${python_version}/site-packagesは/mylib/.venv/lib/python/site-packagesと展開されています!
変数python_versionが空文字列になっているんですね。
これを調べる中でARGにはスコープがあることを知りました。
ARGのスコープ
https://docs.docker.com/reference/dockerfile/#scope
To use an argument in multiple stages, each stage must include the ARG instruction.
手を動かしての理解は以下です。
ARG python_version
FROM ghcr.io/astral-sh/uv:python${python_version}-bookworm AS builder
WORKDIR /mylib
ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
COPY . /mylib/
RUN --mount=type=cache,target=/root/.cache/uv \
+ echo "python_version=${python_version}" && \
uv sync --frozen --no-dev --no-editable
FROM python:${python_version}-slim-bookworm
+ARG python_version
COPY --from=builder /mylib/.venv/lib/python${python_version}/site-packages /usr/local/lib/python${python_version}/site-packages
CMD ["python", "-m", "mylib"]
- 1行目の
ARG python_versionは、FROMで使っている変数python_versionの指定- global scopeと言われるらしい1
- ステージごとに
ARGが必要- 最終ステージは
ARG python_versionを追加することで、COPYできるようになる builderステージに動作確認目的で変数python_versionのechoを追加したところ、これは空文字列builderステージにもARG python_versionを追加すると、コマンドラインから--build-argで渡した値となる
- 最終ステージは
% docker build --no-cache --build-arg python_version=3.11 --progress plain -t uv-practice-lib:py3.11-0.4.0 . % docker run --rm uv-practice-lib:py3.11-0.4.0 Hello from mylib! ['kokoro', 'aki', 'fuka', 'rion', 'subaru', 'masamune', 'ureshino'] % docker build --no-cache --build-arg python_version=3.12 --progress plain -t uv-practice-lib:py3.12-0.4.0 . % docker run --rm uv-practice-lib:py3.12-0.4.0 Hello from mylib! ['kokoro', 'aki', 'fuka', 'rion', 'subaru', 'masamune', 'ureshino'] % docker build --no-cache --build-arg python_version=3.13 --progress plain -t uv-practice-lib:py3.13-0.4.0 . % docker run --rm uv-practice-lib:py3.13-0.4.0 Hello from mylib! ['kokoro', 'aki', 'fuka', 'rion', 'subaru', 'masamune', 'ureshino']
複数のPythonバージョンで、マルチステージビルドでDockerイメージをビルドできた〜!🙌
終わりに
マルチステージビルドでのARG、完全に理解した!
ARGにはスコープがあるFROMより前に書いたARGは、FROMで使っている変数にのみ有効(な挙動に見えている)- ビルド時に渡される変数の値を使いたい場合、ステージごとに
ARGを書く
マルチステージビルドの例では、FROMより前と最終イメージの2箇所でARGを書くことになりました。
ソースコードの全体像はこちらです。
-
global scopeが
FROMにのみ有効というような記載は、ドキュメントにまだ見つけられていません↩