cheat sheet
pip vs uv
Side-by-side feature comparison of pip and uv — speed, lock files, virtual envs, Python version management, and when to use each.
What it is
pip and uv are both Python package managers, but they take different approaches. pip is the bundled standard — pure Python, sequential resolver, ships with every CPython install. uv is a Rust-based drop-in replacement from Astral that resolves and installs packages 10–100× faster, includes a built-in cross-platform lock file, and manages virtual environments and Python versions itself.
Feature comparison
pip ships with Python and is universally supported; uv is a Rust-based drop-in replacement that resolves and installs packages 10–100× faster with a built-in lock file and virtual-env management.
| Feature | pip | uv |
|---|---|---|
| Speed | Moderate — pure Python resolver | 10–100× faster — Rust resolver with global cache |
| Install | python -m ensurepip (bundled) | curl -LsSf https://astral.sh/uv/install.sh | sh |
| Install package | pip install requests | uv pip install requests or uv add requests |
| Create venv | python -m venv .venv | uv venv (creates .venv automatically) |
| Lock file | pip freeze > requirements.txt (manual) | uv.lock — cross-platform, auto-updated |
| Dependency resolution | Sequential, can be slow on large graphs | Parallel, PubGrub algorithm |
| Python version management | Not built-in — needs pyenv | uv python install 3.12 — built-in |
| Script runner | Not available | uv run script.py — inline deps via # /// script |
| Ecosystem compatibility | Universal — every Python tool supports it | High and growing — drop-in for most pip workflows |
| Offline / air-gapped use | Fine with --no-index and local wheels | Global cache helps; same flags supported |
| CI integration | Natively available on all CI runners | Requires install step; GitHub Action available |
When to use pip
Stick with pip when you need maximum ecosystem compatibility, are on a locked-down CI runner where you can't install extra tools, or are distributing a tool that must work in any Python environment with no setup.
When to use uv
Choose uv for new projects. The speed difference is dramatic on cold installs, the built-in lock file is more reliable than requirements.txt, and uv run eliminates the need for a separate venv activation step in scripts and CI.
Migration
Switching from pip to uv is largely drop-in for existing workflows — most pip subcommands are available as uv pip equivalents.
# Old
pip install -r requirements.txt
# New
uv pip install -r requirements.txt
# or, with a project managed by uv
uv sync
Output: (none — exits 0 on success)
Architecture differences
pip is a pure-Python application that ships with the interpreter (python -m pip), shares the same process model as the Python it installs into, and resolves dependencies sequentially using a backtracking resolver. uv is a standalone Rust binary that lives outside the interpreter, downloads its own copies of Python when asked, and resolves dependency graphs in parallel using the PubGrub algorithm.
That distinction has practical consequences. pip's resolver pauses to download and parse each candidate distribution before evaluating constraints; uv pre-fetches metadata in parallel, deduplicates wheels across projects through a content-addressed global cache, and hard-links files into virtual environments instead of copying.
| Layer | pip | uv |
|---|---|---|
| Language | Python (bundled with CPython) | Rust (single static binary, ~30 MB) |
| Resolver | Backtracking, sequential metadata fetch | PubGrub, parallel metadata fetch |
| Cache | Wheel cache (~/.cache/pip) | Content-addressed global cache (~/.cache/uv) |
| Install mode | Copies files into site-packages | Hard-links from cache (falls back to copy) |
| Lockfile semantics | None native (relies on pip freeze) | Universal uv.lock (multi-platform) |
| Python interpreter | Uses whatever Python invoked pip | Can download CPython / PyPy on demand |
| Concurrency | Single-threaded download + install | Parallel download, build, and install |
Performance benchmarks
These numbers vary by network and disk, but the orders of magnitude are stable across machines. Times below are taken on a cold cache for a Django + DRF + Celery + pandas stack (~140 packages).
| Scenario | pip | uv | Speedup |
|---|---|---|---|
| Cold install (no cache) | 48s | 4.1s | ~12× |
| Warm install (cache hit) | 12s | 0.4s | ~30× |
Resolve only (pip-compile vs uv lock) | 22s | 0.6s | ~36× |
| Create venv | 1.2s | 0.05s | ~24× |
pip install -e . editable | 8s | 0.9s | ~9× |
The biggest practical win is in CI: a job that previously took 60–90s for pip install -r requirements.txt typically drops to under 10s with uv pip install, and to ~3s if the cache layer is preserved between runs.
Install commands side-by-side
These are the most common commands developers reach for daily. uv mirrors pip's surface under uv pip, but it also offers higher-level project commands (uv add, uv sync) that pip has no equivalent for.
| Task | pip | uv (drop-in) | uv (native) |
|---|---|---|---|
| Install one package | pip install requests | uv pip install requests | uv add requests |
| Install dev dep | pip install pytest | uv pip install pytest | uv add --dev pytest |
| Install from file | pip install -r requirements.txt | uv pip install -r requirements.txt | uv sync |
| Editable install | pip install -e . | uv pip install -e . | uv sync (auto-detects) |
| Uninstall | pip uninstall requests | uv pip uninstall requests | uv remove requests |
| List installed | pip list | uv pip list | uv tree |
| Freeze | pip freeze | uv pip freeze | uv export --format requirements.txt |
| Show info | pip show requests | uv pip show requests | uv pip show requests |
| Compile (pin) | pip-compile reqs.in | uv pip compile reqs.in | uv lock |
| Upgrade | pip install -U requests | uv pip install -U requests | uv lock --upgrade-package requests |
| Cache info | pip cache info | uv cache info | uv cache info |
| Cache clear | pip cache purge | uv cache clean | uv cache clean |
Lockfile differences
A lockfile records the exact set of packages and versions resolved for a project so every machine and CI run installs the same dependency graph. pip has no native lockfile — teams glue together pip freeze (post-install snapshot) or pip-tools (pre-install resolution) to approximate one. uv ships a real lockfile (uv.lock) by default.
pip's approximation — requirements.txt + pip-tools
# requirements.in (human-edited)
fastapi
sqlalchemy>=2
pytest
# Compile to a fully pinned requirements.txt
pip-compile --generate-hashes requirements.in
Output of requirements.txt:
# This file is autogenerated by pip-compile with Python 3.12
fastapi==0.111.1 \
--hash=sha256:...
sqlalchemy==2.0.30 \
--hash=sha256:...
Gotchas: the resulting file is tied to one Python version and one platform. A requirements.txt produced on macOS may not install cleanly on Linux because conditional wheels (platform_machine, sys_platform) are not separated by marker.
uv's lockfile — uv.lock
# Edit pyproject.toml (or use uv add) and then:
uv lock
Output:
Resolved 28 packages in 142ms
Snippet from uv.lock:
version = 1
requires-python = ">=3.10"
[[package]]
name = "fastapi"
version = "0.111.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "starlette" },
]
[[package.wheels]]
url = "https://files.pythonhosted.org/packages/.../fastapi-0.111.1-py3-none-any.whl"
hash = "sha256:..."
uv.lock is universal: a single file holds resolutions for every Python version and platform the project supports (declared via requires-python and tool.uv.environments). Switching from a macOS dev box to a Linux CI runner reuses the same lock.
Side-by-side summary
| Aspect | pip / pip-tools | uv |
|---|---|---|
| File name | requirements.txt | uv.lock |
| Generated by | pip-compile (third-party) | uv lock (built in) |
| Format | Flat pinned list | TOML with package metadata + wheels |
| Cross-platform | One file per platform | Single file, all platforms |
| Hashes | Optional (--generate-hashes) | Always included |
| Update mechanism | Re-run pip-compile | uv lock --upgrade / uv sync --upgrade |
| Editable installs | Awkward | First-class |
| Read-only install | pip install -r requirements.txt | uv sync --frozen |
Migration: pip to uv
Most pip workflows have a one-line uv equivalent. The biggest win comes from also adopting uv.lock and dropping requirements.txt — but you can do that as a second step.
Stage 1 — drop-in replacement
Replace pip with uv pip and keep requirements.txt. Zero workflow change for the rest of the team.
# Before
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# After
uv venv
source .venv/bin/activate
uv pip install -r requirements.txt
Output: (none — exits 0 on success)
Stage 2 — adopt uv project mode
Move dependencies into pyproject.toml under [project] and let uv.lock replace requirements.txt.
# Initialize project metadata if you don't have a pyproject.toml
uv init --no-readme
# Import existing requirements.txt
uv add $(cat requirements.txt)
uv add --dev $(cat requirements-dev.txt)
# Sync — installs into .venv and writes uv.lock
uv sync
Output:
Resolved 42 packages in 380ms
Installed 42 packages in 121ms
Stage 3 — update CI
GitHub Actions:
- name: Install uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true
- name: Install dependencies
run: uv sync --frozen
- name: Run tests
run: uv run pytest
--frozen refuses to update uv.lock — required in CI so a stale lockfile fails the build rather than silently regenerating.
Stage 4 — Docker
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --no-install-project
COPY . .
RUN uv sync --frozen --no-dev
CMD ["uv", "run", "python", "-m", "myapp"]
The two-stage uv sync lets you cache the dependency layer and only re-install when uv.lock changes.
Ecosystem compatibility
Every Python tool — IDE plugins, linters, CI providers, deployment platforms — assumes pip is available. uv made compatibility a first-class goal: uv pip accepts the same flags pip does, uv.lock exports cleanly to requirements.txt, and the same pyproject.toml works with both tools.
| Tool / platform | Works with pip | Works with uv |
|---|---|---|
| PyPI | ✅ | ✅ |
| Private indexes (Artifactory, Nexus) | ✅ | ✅ (--index-url, [tool.uv.index]) |
pyproject.toml (PEP 621) | ✅ (read-only) | ✅ (read + write) |
setup.py legacy | ✅ | ✅ |
| Hash-checking mode | ✅ | ✅ (always on for uv.lock) |
| Constraints files | ✅ (-c) | ✅ (uv pip install -c) |
Editable installs (-e .) | ✅ | ✅ |
| Conda environments | ✅ | ✅ (treats conda env like a venv) |
| pyenv-managed Pythons | ✅ | ✅ (or use uv python install) |
Docker python:slim | ✅ | ✅ (copy binary from ghcr.io/astral-sh/uv) |
| GitHub Actions | ✅ (setup-python) | ✅ (astral-sh/setup-uv) |
| GitLab / CircleCI / Jenkins | ✅ | ✅ (install via shell one-liner) |
| Read the Docs | ✅ | ✅ (declared in .readthedocs.yaml) |
| AWS Lambda layers | ✅ | ✅ (build wheels with uv pip install --target) |
| Azure / GCP Functions | ✅ | ✅ |
Common pitfalls
uv pip installoutside a venv — without an active virtual environment,uv pip installrefuses to install into the system Python by default. pip will happily install (and possibly break system tools). Always create a venv first.
Mixing
pip installanduv pip installin the same venv — both tools manage the samesite-packages, so this works, but you loseuv.lockintegrity if you bypass uv for an install. Pick one and stick with it per project.
uv addwrites topyproject.toml— there is nouv pipequivalent that updates project metadata. Useuv addfor the project workflow anduv pip installonly for ad-hoc one-offs.
requirements.txtexported fromuv exportmay include uv-specific markers — like; python_version >= "3.10". Most pip versions handle these fine, but old pip (<22) can choke. Pin a recent pip in legacy environments.
uv respects
PIP_INDEX_URL,PIP_EXTRA_INDEX_URL, and otherPIP_*environment variables, so existing private-registry setups carry over without change.
If your team is mid-migration, keep both
requirements.txtanduv.lockfor a release cycle. Generaterequirements.txtfromuv.lockin CI withuv export --format requirements-txt > requirements.txtso pip users aren't blocked.
Real-world recipes
Recipe — port a pip-tools workflow to uv
Goal: drop pip-tools and consolidate on uv while keeping the rest of the team productive.
# 1. Import existing requirements.in into pyproject.toml
uv init --no-readme
uv add -r requirements.in
uv add --dev -r requirements-dev.in
# 2. Generate uv.lock (replaces requirements.txt)
uv lock
# 3. Generate a backward-compatible requirements.txt for pip users
uv export --format requirements-txt --no-dev --frozen > requirements.txt
uv export --format requirements-txt --frozen > requirements-dev.txt
# 4. Add a Makefile target so both groups can install
make install # uv sync --frozen
make install-pip # pip install -r requirements.txt
Output: (none — exits 0 on success)
Recipe — fast Dockerfile with uv
Goal: minimize image size and build time while keeping the dependency layer cached.
FROM python:3.12-slim AS base
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
ENV UV_LINK_MODE=copy UV_COMPILE_BYTECODE=1
WORKDIR /app
FROM base AS deps
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev --no-install-project
FROM deps AS final
COPY . .
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev
CMD ["uv", "run", "python", "-m", "myapp"]
UV_LINK_MODE=copy is required inside Docker because hard-links across BuildKit layers fail; UV_COMPILE_BYTECODE=1 pre-compiles .pyc files for faster cold start.
Recipe — drop-in replacement on a constrained CI runner
Goal: use uv's speed without changing your repository at all (e.g., short-lived experimental runner).
# install uv just for this run
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
# everything else uses existing pip workflow
uv pip install -r requirements.txt
pytest
Output:
============================= test session starts ==============================
platform linux -- Python 3.12.1, pytest-8.2.0, pluggy-1.5.0
rootdir: /home/alice/code/myproject
collected 42 items
tests/test_app.py ........................................ [100%]
============================== 42 passed in 1.32s ==============================
Recipe — Python version matrix without pyenv
# install three Python versions
uv python install 3.10 3.11 3.12
# run pytest under each
for V in 3.10 3.11 3.12; do
uv venv --python "$V" ".venv-$V"
uv pip install --python ".venv-$V/bin/python" -r requirements.txt
".venv-$V/bin/python" -m pytest
done
Output:
Installed 3 versions in 4.21s
+ cpython-3.10.13-linux-x86_64-gnu
+ cpython-3.11.9-linux-x86_64-gnu
+ cpython-3.12.4-linux-x86_64-gnu
============================== 42 passed in 1.18s ==============================
============================== 42 passed in 1.09s ==============================
============================== 42 passed in 1.04s ==============================
This replaces a typical pyenv install 3.10.13 + tox setup with a single tool.
Decision flowchart
- Starting a new project in 2026 → uv. Speed and the built-in lockfile pay off immediately.
- Existing project, team comfortable with pip → pip + pip-tools, or migrate gradually (Stage 1 above).
- CI pipelines on tight time budgets → uv with cache. The speedup is most visible in CI.
- Distributing a library or CLI on PyPI → either. Both build identical wheels; pick by team preference.
- Air-gapped corporate environment with curated mirror → either. pip is more universally trusted; uv works fine with
--index-url. - Need Python interpreter management → uv (
uv python install) or pair pip with pyenv. - Locked-down runner where you can't install binaries → pip. uv requires a 30 MB binary that some compliance teams block.
- Mono-repo with multiple Python projects sharing wheels → uv workspaces. pip has no equivalent.