
uvとは、Pythonのパッケージのインストール、仮想環境の作成、Python本体のバージョン管理までを1つのコマンドで扱うツールです。Astral社がRustで開発しているOSSで、これまでpip・venv・pyenv・pipx・pip-toolsに分かれていた作業をuv addやuv runにまとめられます。
以下のコマンドと出力は、執筆時点の最新版uv 0.12.18をUbuntu 24.04のコンテナで実行して確かめたものです。pip+requirements.txtやpyenvからの移行手順、つまずいたときのエラー文、速さの測り方までを順に扱います。
この記事でわかること
- pip・venv・pyenv・pipxの作業が、uvのどのコマンドに置き換わるか
uv init・uv add・uv runとuv.lockによるプロジェクト管理(0.12系の既定レイアウトを含む)- requirements.txtから移行する手順と、
uv pipで従来の手順のまま速くする方法 - 実測したpipとの速度差と、.pycを作るかどうかで差が大きく変わる理由
- 社内プロキシ・社内インデックス・CIで必要な設定と、よく出るエラーの直し方
uvとは:pip・venv・pyenvとの役割の違い
uvは「どのPythonで」「どのパッケージを」「どこに入れるか」を、pyproject.tomlとuv.lockの2ファイルで管理します。従来のやり方ではPython本体をpyenv、仮想環境をvenv、パッケージをpip、CLIツールをpipx、バージョン固定をpip-toolsが受け持ち、設定もバラバラでした。作業ごとの対応は次のとおりです。
| やりたいこと | 従来の道具 | uvのコマンド |
|---|---|---|
| Python本体の導入・切り替え | pyenv install / pyenv local | uv python install 3.12 / uv python pin 3.12 |
| 仮想環境の作成 | python -m venv .venv | uv venv(プロジェクトでは自動で作成) |
| パッケージの追加 | pip install+requirements.txtを手で編集 | uv add requests |
| バージョンの固定 | pip freeze / pip-compile | uv lock(uv.lockを生成) |
| 別のマシンで環境を再現 | pip install -r requirements.txt | uv sync --locked |
| CLIツールの導入 | pipx install ruff | uv tool install ruff / uvx ruff |
| 仮想環境の中で実行 | source .venv/bin/activateしてから実行 | uv run pytest |
仕組みの面で大きく違うのは、ロックファイルと共有キャッシュです。uv.lockには依存ツリー全体のバージョンとハッシュが入り、Linux・macOS・Windows向けのホイールもまとめて記録されるため、OSが違うメンバーでも同じ解決結果を共有できます。ダウンロードしたパッケージは~/.cache/uvに1回だけ保存され、各プロジェクトの.venvへはLinuxならハードリンク、macOSならクローン(コピーオンライト)で展開されます。プロジェクトを10個作っても、同じバージョンのnumpyがディスクを10回分消費しないのはこのためです。
uvのインストールと更新
Linux・macOSでは公式のインストールスクリプトを使うのが手早く、~/.local/binにuvとuvxが置かれます。スクリプトはシェルの設定ファイルにPATHの追記もしますが、反映されるのは次のログインからなので、その場ではsourceで読み込みます。
curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env
uv --version
uv self update # スクリプトで入れた場合の更新方法Windowsは、PowerShell版のスクリプトかwingetで入れます(どちらも執筆時点の公式手順)。
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
# もしくは
winget install --id=astral-sh.uv -e社内ルールで「スクリプトをパイプでshに渡す導入」が禁止されている場合は、pipx install uvやpip install uvでも同じバイナリが入ります。その場合はuv self updateではなくpipx/pip側で更新してください。導入方法ごとに更新手順が違うので、チーム内でどちらかに統一しておくと、バージョン違いによるuv.lockの差分を減らせます。
uv python installでPython本体を管理する
uv python listで入手できるバージョンの一覧が見られ、uv python install 3.12でビルド済みのPython(python-build-standaloneプロジェクトの配布物)がダウンロードされます。ソースからビルドするpyenvと違ってgccやlibssl-devを用意する必要がなく、検証環境では32.6MiBのダウンロードと1.4秒ほどのインストールで終わりました。
uv python list # 入手可能なもの・導入済みのもの
uv python install 3.12 # 3.12系の最新パッチを導入
uv python install 3.12 --default # python / python3 コマンドも作る
uv python pin 3.12 # カレントに .python-version を書くDownloading cpython-3.12.14-linux-x86_64-gnu (download) (32.6MiB)
Downloaded cpython-3.12.14-linux-x86_64-gnu (download)
Installed Python 3.12.14 in 1.37s
+ cpython-3.12.14-linux-x86_64-gnu (python3.12)導入先は~/.local/share/uv/pythonで、/usr/bin/python3のようなOSのPythonには手を触れません。aptが依存しているPythonを置き換えないので、OSのツールを壊す心配がないのが実務上の利点です。~/.local/binにはpython3.12という名前のリンクだけが作られ、--defaultを付けたときだけpythonとpython3も作られます。インストール後にwarning: `/root/.local/bin` is not on your PATHと出たら、このリンクの置き場所がPATHに入っていない合図です。
使うPythonは、明示した--pythonとカレントから上位へ探した.python-versionが優先され、どちらもなければpyproject.tomlのrequires-pythonを満たすものが選ばれます。条件に合うものが手元になければ自動でダウンロードされます。pyenvの.python-versionは同じ書式なのでそのまま読まれますが、.bashrcにpyenv initが残っていると、シェルで打つpythonだけがpyenvのものを指し続けます。移行を終えたらpyenvの初期化行を消しておくと、どちらのPythonが動いているか迷わずに済みます。
uv init・uv add・uv runでプロジェクトを作る
新しいプロジェクトはuv initで作ります。執筆時点の0.12系では、名前を付けたuv initがsrc/レイアウトのパッケージ構成を作り、ビルドバックエンド(uv_build)と実行コマンド([project.scripts])まで登録します。解説記事によってはmain.pyだけの構成が紹介されていますが、それが欲しい場合は後述の--app --no-packageを付けます。
uv init hello-api --python 3.12
cd hello-api
uv add requests # 依存を追加(.venv と uv.lock もここで作られる)
uv add --dev pytest ruff # 開発用グループ(dev)に追加
uv run hello-api # [project.scripts] に登録されたコマンドを実行
uv run pytest # .venv の中で実行(activate は不要)
uv tree # 依存ツリーを表示[project]
name = "hello-api"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"requests>=2.34.2",
]
[project.scripts]
hello-api = "hello_api:main"
[build-system]
requires = ["uv_build>=0.12.18,<0.13.0"]
build-backend = "uv_build"
[dependency-groups]
dev = [
"pytest>=9.1.1",
"ruff>=0.16.8",
]Resolved 13 packages in 1ms
hello-api v0.1.0
├── requests v2.34.2
│ ├── certifi v2026.7.22
│ ├── charset-normalizer v3.5.1
│ ├── idna v3.20
│ └── urllib3 v2.8.0
├── pytest v9.1.1 (group: dev)
│ ├── iniconfig v2.3.0
│ ├── packaging v26.3
│ ├── pluggy v1.6.0
│ └── pygments v2.21.0
└── ruff v0.16.8 (group: dev)uv addは、pyproject.tomlへの追記、依存の解決、uv.lockの更新、.venvへの反映を1回でこなします。pyproject.tomlにはrequests>=2.34.2のような下限だけが書かれ、実際に入った正確なバージョンはuv.lockが持つ、という分担です。uv.lockはGitにコミットし、手では編集しません。uv runは実行の直前にロックと.venvがずれていないか確かめ、ずれていれば同期してからコマンドを動かします。
構成の選び方は「インストールして配るものか」で決めます。社内に配るCLIやライブラリなら既定のパッケージ構成、サーバーに置いて動かすだけのスクリプトやWebアプリならuv init --app --no-packageの平らな構成(pyproject.tomlとmain.py)が扱いやすいです。後者はビルドの工程がないぶんuv syncが軽く、Dockerfileも短くなります。pyproject.tomlだけを作る--bareは、既存リポジトリの移行で使います。
Pythonが1つも入っていないマシンで--pythonなしにuv initすると、その時点の最新(検証時は3.14.7)が自動で選ばれ、requires-python = ">=3.14"が書き込まれます。本番サーバーが3.12なら、この時点で配備できないプロジェクトになります。本番に合わせたバージョンをuv init --python 3.12のように最初から指定してください。
環境を再現するuv syncは、ロックとpyproject.tomlのずれをどう扱うかで3通りに使い分けます。
| コマンド | pyproject.tomlとuv.lockがずれていたとき | 向いている場面 |
|---|---|---|
uv sync | ロックを更新してから同期する | 手元の開発 |
uv sync --locked | エラーで止まる | CI・本番への配備(ロックのコミット漏れを検出) |
uv sync --frozen | ずれを確認せず、ロックの内容で入れる | Dockerビルドの途中などpyproject.tomlの確認が不要な段階 |
pip・requirements.txtから移行する手順
既存のリポジトリは、requirements.txtの中身をpyproject.tomlのdependenciesへ取り込むのが基本です。--bareでpyproject.tomlだけを作り、uv add -rで読み込みます。開発用のrequirements-dev.txtがあれば--dev付きで別グループに入れます。
cd legacy-app
uv init --bare --python 3.12
uv add -r requirements.txt
uv add --dev -r requirements-dev.txt
uv python pin 3.12
git add pyproject.toml uv.lock .python-version検証では、requests==2.32.3のような完全固定はそのまま、PyYAML>=6.0のような範囲指定は範囲のまま(名前は小文字のpyyamlに正規化)dependenciesに入り、実際のバージョンはuv.lockで固定されました。requirements.txtを前提にした仕組み、たとえば古いサーバーの配備スクリプトや他チームのCIがまだ残る場合は、uv.lockから書き出したファイルを並べて置きます。
uv export --format requirements-txt --no-hashes --no-dev > requirements.lock.txtpyproject.tomlへ移る準備ができていない段階でも、uv pipを使えば従来の手順のまま速くなります。uv venvで環境を作り、pip installをuv pip installに置き換えるだけです。ただしuv venvで作った環境にはpip自体が入らないため、pipを直接呼ぶ古いスクリプトがあるなら--seedを付けます。
uv venv --python 3.12 # .venv を作成(pip も欲しいなら --seed)
source .venv/bin/activate
uv pip install -r requirements.txt
uv pip freezeCLIツールと単体スクリプト:uv tool・uvx・インラインメタデータ
ruffやpre-commitのように特定のプロジェクトに属さないCLIは、uv tool installでツールごとの専用環境に入れます。コマンドは~/.local/binに置かれ、ツール同士の依存がぶつかりません。一度だけ試したいものはuvxで一時環境を作って実行し、何も残しません。
uv tool install ruff # ~/.local/bin/ruff が作られる
uv tool list
uv tool upgrade --all
uvx pycowsay hello # 一時環境で実行するだけ1ファイルで完結する運用スクリプトは、依存をファイル先頭のコメント(PEP 723のインラインメタデータ)に書けます。uv add --scriptで追記され、uv runするとその依存を入れた一時環境で動きます。サーバーに.py1枚を置けば済み、仮想環境を作る手順書が不要になるので、情シスの定型作業スクリプトと相性がよい方式です。
uv init --script check_url.py --python 3.12
uv add --script check_url.py requests
uv run check_url.py https://www.python.org/# /// script
# requires-python = ">=3.12"
# dependencies = [
# "requests>=2.34.2",
# ]
# ///
import sys
import requests
url = sys.argv[1] if len(sys.argv) > 1 else "https://pypi.org/simple/"
r = requests.get(url, timeout=10)
print(r.status_code, url)uvはどれくらい速いか:実測値と測り方
uvは速さが売りのツールですが、差の大きさは条件でかなり変わります。requests・pandas・fastapi・uvicorn・sqlalchemy・pydanticの6つ(依存込みで23パッケージ)を新しい仮想環境へ入れる時間を、pip 25.0.1とuv 0.12.18で計りました。
| 条件 | pip | uv |
|---|---|---|
| キャッシュなし | 15.1〜16.3秒(既定:.pycを作る) | 0.84〜1.28秒(既定:.pycを作らない) 6.7〜8.8秒( --compile-bytecode) |
| キャッシュあり・.pycを作る | 16.1〜17.8秒(既定) | 3.6〜7.4秒(--compile-bytecode) |
| キャッシュあり・.pycを作らない | 5.2〜5.9秒(--no-compile) | 0.10〜0.12秒(既定) |
条件は執筆時点、python:3.12-slimのコンテナ(4 vCPU、メモリ1GB制限)、同じ回線で各3回計った範囲です。読むときに押さえたい点は2つあります。1つ目は、uvが既定で.pyc(バイトコード)を作らないことです。pipはインストール時に全.pyファイルをコンパイルするため、pipの既定とuvの既定をそのまま比べると差が実態より大きく見えます。2つ目は、両方とも.pycを作る条件にそろえてもuvが2〜5倍速かったことで、並列ダウンロードと共有キャッシュからの展開が効いています。
.pycは初回のimport時に作られるので、開発環境で事前に作らなくても困りません。起動時間が気になるDockerイメージやサーバーへの配備では、環境変数UV_COMPILE_BYTECODE=1を付けてインストール時に作らせます。自分の環境での差は、次のようにキャッシュを消してからtimeで比べると確かめられます。uv cache cleanは全プロジェクト共通のキャッシュを消すので、計測が終わった直後のuv syncは再ダウンロードになります。
printf 'requests\npandas\nfastapi\n' > req.txt
# pip:新しい仮想環境に、キャッシュを使わず入れる
rm -rf /tmp/v-pip && python3 -m venv /tmp/v-pip
time /tmp/v-pip/bin/pip install -q --no-cache-dir -r req.txt
# uv:キャッシュを消してから入れる(もう一度実行するとキャッシュありの値)
uv cache clean
rm -rf /tmp/v-uv && uv venv -q /tmp/v-uv
time uv pip install -q --python /tmp/v-uv/bin/python -r req.txt社内ネットワークとCIで使うときの設定
社内のPyPIミラー(NexusやdevpiなどのSimple API互換サーバー)から取得する場合は、pyproject.tomlに[[tool.uv.index]]を書くか、環境変数UV_DEFAULT_INDEXで既定の取得先を差し替えます。uvはpip.confやPIP_INDEX_URLを読まないので、pipの設定を残したままでは社内ミラーを経由しません。旧名のUV_INDEX_URLも執筆時点では非推奨扱いです。
[[tool.uv.index]]
name = "internal"
url = "https://pypi.example.internal/simple"
default = true # PyPI の代わりにこのインデックスを既定にする通信を中継して検査するプロキシの配下では、証明書エラーでダウンロードが止まることがあります。uvは既定で自分が同梱する証明書リストを使い、OSに登録した社内CAを見ません。--system-certs(環境変数はUV_SYSTEM_CERTS=1、以前の名前は--native-tls)でOSの証明書ストアを使わせるか、社内CAを含むファイルをSSL_CERT_FILEで指定します。プロキシ自体はHTTPS_PROXYで指定します。
CIではロックの更新漏れを見逃さないようuv sync --lockedを使います。GitHub Actionsならastral-sh/setup-uv(執筆時点の最新はv10系)でuvを入れ、あとは手元と同じコマンドを並べるだけです。ワークフロー全体の書き方はGitHub Actionsで始めるCI/CDにまとめています。
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v10
- run: uv sync --locked
- run: uv run pytest -qつまずきポイント
| 症状・エラーメッセージ | 原因 | 対処 |
|---|---|---|
error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. | pyproject.tomlを手で編集したが、uv lockしていないuv.lockをコミットした | 手元でuv lockを実行し、更新されたuv.lockをコミットする。依存の変更はuv add/uv removeで行えばずれない |
error: No virtual environment found; run `uv venv` to create an environment, or pass `--system` to install into a non-virtual environment | uv pip installを仮想環境の外で実行した | uv venvで作ってから実行する。コンテナ内などでシステムのPythonへ入れたいときだけ--systemを付ける |
No module named pip | uv venvで作った環境には既定でpipが入らない | uv pipを使うか、uv venv --seed --clearで作り直す |
A virtual environment already exists at: .venv | 0.12系のuv venvは既存の.venvを黙って上書きしない | 作り直すなら--clearを付ける(環境変数UV_VENV_CLEAR=1でも可) |
error: The requested Python version `3.13` is incompatible with the project `requires-python` value of `>=3.14`. | uv python pinで指定した版がrequires-pythonの範囲外 | pyproject.tomlのrequires-pythonを先に直してからuv python pinする |
動作確認
最後に、使われているPythonが.venvのものか、ロックと環境がそろっているかを確かめます。uv lock --checkは終了コード0なら更新不要、uv sync --lockedはずれがあればエラーで止まるので、この2行はCIにもそのまま使えます。
$ uv --version
uv 0.12.18 (x86_64-unknown-linux-gnu)
$ uv run python -c 'import sys; print(sys.executable, sys.version)'
/work/hello-api/.venv/bin/python 3.12.14 (main, Sep 1 2026, 14:16:52) [Clang 22.1.3 ]
$ uv lock --check
Resolved 8 packages in 1ms
$ uv sync --locked
Resolved 8 packages in 1ms
Checked 7 packages in 0.36mssys.executableがプロジェクト直下の.venv/bin/pythonを指していれば、uvの管理下で動いています。VS Codeなどのエディタでも、この.venv/bin/pythonをインタープリターに選べば補完やテスト実行が同じ環境を使います。
よくある質問
PythonのUVは公式ですか?
uvはAstral社が開発するオープンソースのツールで、Python本体を管理するPSF(Python Software Foundation)の公式ツールではありません。ただしpyproject.tomlや依存の書き方はPEPで定められた標準の形式に沿っているため、uvで作ったプロジェクトはpipなど他のツールからもインストールできます。
UVとpipの違いは何ですか?
pipはパッケージを入れることが中心で、仮想環境はvenv、Pythonのバージョンはpyenvなど別の道具に任せます。uvはそれに加えてuv.lockによるバージョン固定とPython本体の導入まで1つで扱い、ダウンロード済みのパッケージを共有キャッシュから展開するため、2回目以降のインストールが特に速くなります。
PythonのUVは商用利用できますか?
uv本体はMITライセンスとApache License 2.0のデュアルライセンスで配布されており(執筆時点、PyPIのパッケージ情報で確認)、社内開発や商用の製品開発でも使えます。注意が必要なのはuvで入れる個々のパッケージのライセンスで、こちらはパッケージごとに確認してください。
UVとvenvの違いは何ですか?
venvは仮想環境(隔離されたsite-packages)を作るだけの標準モジュールです。uvも内部では同じ形式の.venvを作りますが、作成・パッケージの導入・ロック・実行を自動でつなぐので、activateせずにuv runで動かせます。uv venvで作った環境には既定でpipが入らない点も違います。
まとめ
uvはpyproject.tomlとuv.lockを軸に、Python本体・仮想環境・パッケージ・CLIツールをまとめて扱います。新規はuv init --python 3.12で本番と同じ版を固定し、既存はuv init --bareとuv add -rで移すのが近道です。CIと本番はuv sync --locked、イメージ作成時はUV_COMPILE_BYTECODE=1を付け、社内ネットワークではUV_DEFAULT_INDEXと--system-certsを最初に確認してください。
次に読む
- Dockerfileの書き方:マルチステージビルド・レイヤーキャッシュ・イメージ軽量化:uvで作った.venvをDockerイメージに載せる手順
- miseでNode.js・Pythonのバージョンを管理する:PythonとNode.jsを同じリポジトリで扱う場合の分担
- pre-commitでコミット前チェックを自動化する:ruffのチェックをコミットのたびに走らせる