2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

uv Python on Docker 最新の書き方 at 2025-10

Last updated at Posted at 2025-10-21

前段

前回の2024年8月の自分の記事 から早1年と2か月。docker uv python などでググると、uvが安定していなかった過去のやり方を記述した検索結果が上位にひっかかるため、現時点でのベストプラクティス(自称)をここに記すこととする。

Disclamer

この記事の内容は、私個人の意見や見解であり、私が所属する組織の公式な立場、方針、意見を反映するものではありません。この記事の内容について、組織はいかなる責任も負いません。

はじめに

https://github.com/astral-sh/uv-docker-example
https://docs.astral.sh/uv/guides/integration/docker/

まずはこの2つのサイトを読めば、これから下の内容を参照する必要がなくなります。

方針

  • Debian 13 (trixie) が2025年8月にリリースされていたのでこれをベースとする
  • python は system python on docker を使い、uv managed python は使わない
  • docker は multi-stage buildを利用する
  • application は noroot user で実行する
  • チームで Mac と WSL2 が混在していても volume オプションの書き込みで問題なく動くようにする

前提

streamlit の実行を想定する。以下のフォルダ構成

$ tree
.
├── compose.yaml
├── Dockerfile
├── pyproject.toml
├── src
│   └── main.py
└── uv.lock

結論

Dockerfile

# syntax=docker/dockerfile:1

ARG PY_VERSION="3.13.9"
ARG UV_VERSION="0.9.4"

# UV image stage
FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv

# -- Builder Stage --
FROM python:${PY_VERSION}-slim-trixie AS builder

ENV UV_SYSTEM_PYTHON=1 \
    UV_LINK_MODE=copy \
    UV_COMPILE_BYTECODE=1

RUN --mount=from=uv,source=/uv,target=/bin/uv \
    --mount=type=bind,source=uv.lock,target=uv.lock \
    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    --mount=type=cache,target=/root/.cache/uv \
    uv export --frozen --no-dev --no-editable | uv pip install -r -

# prod
FROM python:${PY_VERSION}-slim-trixie AS prod

ARG PY_VERSION
ARG PY_VERSION_MINOR=${PY_VERSION%.*}

ENV PYTHONUNBUFFERED=1

WORKDIR /src

COPY --from=builder \
  /usr/local/lib/python${PY_VERSION_MINOR}/site-packages \
  /usr/local/lib/python${PY_VERSION_MINOR}/site-packages

# ↓need to copy cli python command
COPY --from=builder /usr/local/bin/streamlit /usr/local/bin/streamlit

# Create a non-privileged user that the app will run under.
# See https://docs.docker.com/go/dockerfile-user-best-practices/
ARG HOST_UID=1000
ARG HOST_GID=1000

# Create a non-root user and group
RUN <<EOF bash -eux
if ! getent group ${HOST_GID} > /dev/null; then
    groupadd -g ${HOST_GID} appgroup;
fi
useradd -u ${HOST_UID} -g ${HOST_GID} -m appuser
EOF

COPY --chown=appuser:appgroup src/ .

# Switch to the non-privileged user to run the application.
USER appuser

# Expose the port that the application listens on.
EXPOSE 8501

# Run the application.
CMD ["streamlit", "run", "main.py", "--browser.gatherUsageStats=false", "--server.headless=true"]

compose.yaml

services:
  server:
    build:
      context: .
      args:
        # 環境変数をビルド時の引数に渡す
        - HOST_UID=${HOST_UID}
        - HOST_GID=${HOST_GID}
    image: uv-python-docker-example
    container_name: uv-python-docker-example
    ports:
      - 8501:8501
    volumes:
      # ローカルのカレントディレクトリをコンテナの /src にマウント
      - ./src:/src

実行

HOST_UID=$(id -u) HOST_GID=$(id -g) docker compose up --build

compose を使わない場合は

docker build --build-arg HOST_UID=$(id -u) --build-arg HOST_GID=$(id -g) -t uv-python-docker-example .
docker run --rm -p 8501:8501 -v ./src:/src uv-python-docker-example

本番環境では HOST_UIDHOST_GID の指定は不要(デフォルトの1000が使用される)

Dockerfileの解説

# syntax=docker/dockerfile:1

docker syntaxディレクティブの有効化


# UV image stage
FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv

マルチステージビルドにて uv イメージの指定。


ENV UV_SYSTEM_PYTHON=1 \

builderステージにて uv でシステム python を利用する環境設定。なお、このオプションは uv sync, uv lock, uv add などには効果がないので、後に uv pip install ... を使うこととする。


    UV_LINK_MODE=copy \

これは cache (docker image 実行では /root/.cache/uv) に保存した package file をどのように実行パス( UV_SYSTEM_PYTHON=1 で設定しているので /usr/local/ に連携するか。copy, hardlink, clone, symlink があり、Dockerfileでは cache からの確実な連携により copy が望ましい。


RUN --mount=from=uv,source=/uv,target=/bin/uv \
    --mount=type=bind,source=uv.lock,target=uv.lock \
    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    --mount=type=cache,target=/root/.cache/uv \
    uv export --frozen --no-dev --no-editable | uv pip install -r -

マルチステージビルドの肝。まず1行目でuvバイナリをマウント。2,3行目で対象ファイルをbind、4行目でcacheを指定、5行目で requirements.txt を作成せずパイプで渡してインストール。--frozen は uv.lock を更新せずそのまま使う。 --no-dev は pyproject.toml の dependency-groups.dev をインストールしない。 --no-editable はプロジェクトを編集不可モードでインストール。requirements.txt を作成せずパイプで渡して uv pip install ... する。


prodステージ

ARG PY_VERSION
ARG PY_VERSION_MINOR=${PY_VERSION%.*}

FROMコマンドは新たな変数スコープを作成する。最初の FROM より前に出現する ARG はグローバルスコープとなる。
一方でグローバルスコープの ARG はマルチステージの各 FROM スコープ(FROMの内側)へは自動的に引き継がれないため、
FROMの内側で再宣言する必要がある(値は引き継がれる)。


ARG HOST_UID=1000
ARG HOST_GID=1000

# Create a non-root user and group
RUN <<EOF bash -ex
if ! getent group ${HOST_GID} > /dev/null; then
    groupadd -g ${HOST_GID} appgroup;
fi
useradd -u ${HOST_UID} -g ${HOST_GID} -m appuser
EOF

まずはnoroot user 対応。WSL2上で開発する際に、volumeオプションでローカル同期した時にdocker側でファイル作成した際にroot実行されたファイルは通常ユーザーと権限が異なり編集や削除等ができなくなる。そのためローカル実行時には compose コマンド実行のように HOST_UID 等の環境変数を指定して実行する。Macではユーザー所属のGID=20がDocker image上のubuntuでUIDとして存在しているのでその確認プロセスを入れている。

RUN の複数行はヒアドキュメントが使える。-eux にてエラー停止、未定義変数エラー、ログ詳細を設定。


COPY --chown=appuser:appgroup src/ .

アプリケーションの権限はrootではなく --chown でユーザー権限を指定するのがセキュリティー的に望ましい。


皆様のお役に立てれば幸いです。

qiita の uv(astral-sh) タグが広まってきてなによりです。

以上

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?