nikkie-ftnextの日記

イベントレポートや読書メモを発信

マルチステージビルドにて --build-arg で渡した値にDockerfileの各ステージでアクセスしたいときは、ステージごとにARGを宣言する(uvで開発するライブラリを例に)

Today I learnedです。
以下の記事のアップデートとなります。

目次

uvを使ったプロジェクトを複数のPythonバージョンでDockerイメージビルドしたい

ここまででマルチステージビルドを使って、uvで環境構築したPythonライブラリのDockerイメージをビルドできるようになっています。
イチオシポイントは、uvを残さないという点です。

今回はARGを使って、uv(やpython)のイメージを切り替えられるようにしてみたいと思ったのです。

  • ghcr.io/astral-sh/uv:python3.11-bookworm
  • ghcr.io/astral-sh/uv:python3.12-bookworm
  • ghcr.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と評価されます。
例えばRUNecho ${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_versionechoを追加したところ、これは空文字列
      • 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を書くことになりました。
ソースコードの全体像はこちらです。


  1. global scopeがFROMにのみ有効というような記載は、ドキュメントにまだ見つけられていません