nikkie-ftnextの日記

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

改訂版:uv init --appで環境構築して開発中の自作Pythonアプリケーションを含んだDockerイメージをビルドする(FastAPIを例に)

はじめに

今宵はfantastic night🌟1 nikkieです。

半年くらい前に書いた、uv init --appしたプロジェクトでDockerイメージを作る記事の改訂版です。

目次

uv init --appしたプロジェクトでDockerイメージを作る

Pythonプロジェクトの雛形を作るコマンドuv initは、--appを指定するとApplicationとしてプロジェクトを作ります。
https://docs.astral.sh/uv/concepts/projects/init/#applications

Application projects are suitable for web servers, scripts, and command-line interfaces.

uv init --appの例は、FastAPIのアプリケーションを作るときです。

docs.astral.sh

過去の記事では、uv init --appしたプロジェクトで、マルチステージビルドuvを残さずにDockerイメージを作りました。

  • マルチステージビルドで成果物イメージからuvは除く
  • 依存ライブラリのコピーと、アプリケーションの配置をした

PythonのDockerイメージのマルチステージビルドはこちらをどうぞ

uv init --appしたFastAPIアプリケーションをDockerイメージにする中で、改訂のきっかけがありました。

Dockerイメージでuvicornコマンドが動かない

% docker --version
Docker version 27.5.1-rd, build 0c97515

Pythonのマルチステージビルドにならって、最初は以下のようにDockerfileを書きました。

ビルドはできます。

% docker build -t uv-practice-fastapi:0.0.1 .

しかし、docker runでコンテナを起動できません。

% docker run --rm -p 8000:8000 uv-practice-fastapi:0.0.1 
exec /usr/local/bin/uvicorn: no such file or directory

このメッセージの理由は、シバンによると理解しました。

% docker run -it --rm -p 8000:8000 uv-practice-fastapi:0.0.1 bash
root@e37442a7b681:/app# cat /usr/local/bin/uvicorn
#!/app/.venv/bin/python
# -*- coding: utf-8 -*-
import sys
from uvicorn.main import main
if __name__ == "__main__":
    if sys.argv[0].endswith("-script.pyw"):
        sys.argv[0] = sys.argv[0][:-11]
    elif sys.argv[0].endswith(".exe"):
        sys.argv[0] = sys.argv[0][:-4]
    sys.exit(main())
# ls /app/.venv/bin/python
ls: cannot access '/app/.venv/bin/python': No such file or directory

PATHに配置した)uvicornコマンドを/app/.venv/bin/pythonで実行しようとしますが、そこにPython処理系はないので実行できず落ちています。
以前の記事では、依存ライブラリのコマンドを使う要素がなかったため、これを見落としていました。

依存ライブラリのコマンドが使えるように注意を払った改訂版

※私の中ではめちゃめちゃ美しく解決できてはいないので、アイデアをお持ちの方がいたらぜひ話しましょう

uvicornコマンドのファイルに書かれたシバンを書き変えることを諦め、/app/.venv/bin/pythonを用意するアプローチを取りました2

FROM python:3.12-slim-bookworm
+WORKDIR /app
+RUN python -m venv .venv --without-pip
-COPY --from=builder /app/.venv/bin/uvicorn /usr/local/bin/uvicorn
+COPY --from=builder /app/.venv/bin/uvicorn /app/.venv/bin/uvicorn
-COPY --from=builder /app/.venv/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
+COPY --from=builder /app/.venv/lib/python3.12/site-packages /app/.venv/lib/python3.12/site-packages
-WORKDIR /app
COPY main.py .
EXPOSE 8000
-CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]
+CMD ["/app/.venv/bin/uvicorn", "main:app", "--host", "0.0.0.0"]

WORKDIR(/app)の下に仮想環境を作り、そこにbuilderイメージから一式コピーしました。
全容はこちらで確認できます3

% docker build -t uv-practice-fastapi:0.1.0 .
% docker run --rm -p 8000:8000 uv-practice-fastapi:0.1.0
% curl http://127.0.0.1:8000/ | jq '.'
{
  "kokoro": "Sunday",
  "aki": "Thursday",
  "fuka": "Thursday",
  "rion": "Sunday",
  "subaru": "Tuesday",
  "masamune": "Tuesday",
  "ureshino": "Friday"
}

ここで作る仮想環境にpipは不要なので、pipなしの仮想環境を作る知識が役に立ちましたね。

uvは含みませんが、uvが作ったディレクトリ構造が残っているのが、個人的にきれいに解決できていないと感じるポイントです。

手を動かす中で、Pythonを始めたときに知った「仮想環境はPC内を移動できない」というのはなぜかが腹落ちしていきました。

終わりに

uvで環境構築したPythonアプリケーションのDockerイメージについて、依存ライブラリのコマンドが使えるように改訂しました。

  • builderステージにてuvでインストールするときに、依存ライブラリのコマンドにuvの仮想環境でシバンが書き込まれる
  • マルチステージビルドで依存ライブラリや依存ライブラリのコマンドをコピーする際、書かれたシバンに注意して配置する

シバンは私には盲点でしたが、体験して仮想環境がmvで動かせない理由を納得できました


  1. ファンタスティック!でした。ありがとうございました
  2. 同僚にこの話をしたところ、uv exportで依存を吐き出して対処するというアイデアを知りました。なるほどと思いました
  3. Dockerfileだけ確認したい場合はこちら