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.

Featurepipuv
SpeedModerate — pure Python resolver10–100× faster — Rust resolver with global cache
Installpython -m ensurepip (bundled)curl -LsSf https://astral.sh/uv/install.sh | sh
Install packagepip install requestsuv pip install requests or uv add requests
Create venvpython -m venv .venvuv venv (creates .venv automatically)
Lock filepip freeze > requirements.txt (manual)uv.lock — cross-platform, auto-updated
Dependency resolutionSequential, can be slow on large graphsParallel, PubGrub algorithm
Python version managementNot built-in — needs pyenvuv python install 3.12 — built-in
Script runnerNot availableuv run script.py — inline deps via # /// script
Ecosystem compatibilityUniversal — every Python tool supports itHigh and growing — drop-in for most pip workflows
Offline / air-gapped useFine with --no-index and local wheelsGlobal cache helps; same flags supported
CI integrationNatively available on all CI runnersRequires 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.

bash
# 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.

Layerpipuv
LanguagePython (bundled with CPython)Rust (single static binary, ~30 MB)
ResolverBacktracking, sequential metadata fetchPubGrub, parallel metadata fetch
CacheWheel cache (~/.cache/pip)Content-addressed global cache (~/.cache/uv)
Install modeCopies files into site-packagesHard-links from cache (falls back to copy)
Lockfile semanticsNone native (relies on pip freeze)Universal uv.lock (multi-platform)
Python interpreterUses whatever Python invoked pipCan download CPython / PyPy on demand
ConcurrencySingle-threaded download + installParallel 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).

ScenariopipuvSpeedup
Cold install (no cache)48s4.1s~12×
Warm install (cache hit)12s0.4s~30×
Resolve only (pip-compile vs uv lock)22s0.6s~36×
Create venv1.2s0.05s~24×
pip install -e . editable8s0.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.

Taskpipuv (drop-in)uv (native)
Install one packagepip install requestsuv pip install requestsuv add requests
Install dev deppip install pytestuv pip install pytestuv add --dev pytest
Install from filepip install -r requirements.txtuv pip install -r requirements.txtuv sync
Editable installpip install -e .uv pip install -e .uv sync (auto-detects)
Uninstallpip uninstall requestsuv pip uninstall requestsuv remove requests
List installedpip listuv pip listuv tree
Freezepip freezeuv pip freezeuv export --format requirements.txt
Show infopip show requestsuv pip show requestsuv pip show requests
Compile (pin)pip-compile reqs.inuv pip compile reqs.inuv lock
Upgradepip install -U requestsuv pip install -U requestsuv lock --upgrade-package requests
Cache infopip cache infouv cache infouv cache info
Cache clearpip cache purgeuv cache cleanuv 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

bash
# 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:

text
# 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

bash
# Edit pyproject.toml (or use uv add) and then:
uv lock

Output:

text
Resolved 28 packages in 142ms

Snippet from uv.lock:

toml
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

Aspectpip / pip-toolsuv
File namerequirements.txtuv.lock
Generated bypip-compile (third-party)uv lock (built in)
FormatFlat pinned listTOML with package metadata + wheels
Cross-platformOne file per platformSingle file, all platforms
HashesOptional (--generate-hashes)Always included
Update mechanismRe-run pip-compileuv lock --upgrade / uv sync --upgrade
Editable installsAwkwardFirst-class
Read-only installpip install -r requirements.txtuv 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.

bash
# 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.

bash
# 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:

text
Resolved 42 packages in 380ms
Installed 42 packages in 121ms

Stage 3 — update CI

GitHub Actions:

yaml
- 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

dockerfile
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 / platformWorks with pipWorks 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 install outside a venv — without an active virtual environment, uv pip install refuses to install into the system Python by default. pip will happily install (and possibly break system tools). Always create a venv first.

Mixing pip install and uv pip install in the same venv — both tools manage the same site-packages, so this works, but you lose uv.lock integrity if you bypass uv for an install. Pick one and stick with it per project.

uv add writes to pyproject.toml — there is no uv pip equivalent that updates project metadata. Use uv add for the project workflow and uv pip install only for ad-hoc one-offs.

requirements.txt exported from uv export may 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 other PIP_* environment variables, so existing private-registry setups carry over without change.

If your team is mid-migration, keep both requirements.txt and uv.lock for a release cycle. Generate requirements.txt from uv.lock in CI with uv export --format requirements-txt > requirements.txt so 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.

bash
# 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.

dockerfile
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).

bash
# 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:

text
============================= 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

bash
# 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:

text
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.