How to Install and Use uv: Fast Python Package Manager

By 

•

Updated on

•

14 min read

Installing and using uv, a fast Python package manager, on Linux

If you have worked with Python for a while, you have probably juggled pip, virtualenv, pip-tools, pipx, and pyenv just to keep a few projects running. Each tool does one thing, and wiring them together is slow and fragile. uv is a single binary that replaces all of them. It is written in Rust, published by Astral (the team behind ruff), and is typically ten to a hundred times faster than pip for common workflows.

This guide explains how to install uv on Linux, create and manage Python projects with it, add dependencies, work with virtual environments, and install specific Python versions.

Quick Reference

CommandDescription
uv init my-projectCreate a new project with pyproject.toml
uv add requestsAdd a dependency and update the lockfile
uv add --dev pytestAdd a development dependency
uv remove requestsRemove a dependency
uv syncInstall dependencies from the lockfile
uv lockUpdate the lockfile
uv run script.pyRun a command in the project environment
uv venvCreate a virtual environment
uv pip install requestsPip-compatible install interface
uv python install 3.12Install a specific Python version
uv python listList installed and available Python versions
uv python pin 3.13Pin the project to a Python version
uv tool install ruffInstall a CLI tool globally
uvx ruff check .Run a tool without installing it permanently
uv self updateUpdate uv to the latest release
uv cache cleanClear the uv cache

Installing uv

The recommended install path on Linux is the standalone script from Astral. It downloads a prebuilt binary, places it in ~/.local/bin, and does not require Python to be installed first:

Terminal
curl -LsSf https://astral.sh/uv/install.sh | sh

If curl is not installed, wget does the same job:

Terminal
wget -qO- https://astral.sh/uv/install.sh | sh

The script prints where it put the binary and whether it updated your shell rc file. Reload your shell or run source ~/.bashrc so the new PATH takes effect, then verify the install:

Terminal
uv --version
output
uv 0.12.12

To pin a specific release instead of always taking the latest one, put the version in the URL. This matters on CI runners and shared build machines, where an unannounced upgrade can change how a lockfile resolves:

Terminal
curl -LsSf https://astral.sh/uv/0.12.12/install.sh | sh

The installer writes to ~/.local/bin by default and may edit your shell profile. Set UV_INSTALL_DIR to send the binary somewhere else, and UV_NO_MODIFY_PATH=1 to leave your shell files untouched. The following command installs uv in the user-writable ~/bin directory:

Terminal
curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="$HOME/bin" UV_NO_MODIFY_PATH=1 sh

Because this command disables automatic PATH changes, add ~/bin to your PATH yourself if it is not already there.

If you prefer to install from a package manager, uv is also available through pip, pipx, and Homebrew:

Terminal
pip install uv
Terminal
pipx install uv
Terminal
brew install uv

On Ubuntu, Debian, and Derivatives, pip may refuse a system-wide install because the Python environment is marked as externally managed. In that case, use pipx install uv or the standalone installer. The guide on pip vs apt explains why modern releases block that install path.

If you already have a Rust toolchain, you can build uv from source, although this takes far longer than downloading the prebuilt binary:

Terminal
cargo install --locked uv

uv in the Ubuntu and Debian Repositories

A frequent question is whether uv can be installed with apt. Ubuntu 24.04, 25.10, and 26.04 do not provide Astral’s uv CLI, and neither do Debian 12 and Debian 13. On these releases, use the standalone installer or pipx instead.

Debian testing and unstable contain a source package named uv, but it currently builds only the python3-uv-build package. That package provides the Python build backend and does not install the uv command. The Ubuntu development series after 26.04 has a separate package for the full CLI, although it trails the upstream release. Until the CLI package reaches the archive you are running, the standalone installer is the practical choice.

Third-party apt repositories that track upstream more closely do exist. They are maintained neither by Astral nor by the distributions, so treat them the way you would treat any other external repository before adding one to a machine that matters.

Installing uv from Snap

On Ubuntu and other distributions running snapd, uv is available from the Snap Store:

Terminal
sudo snap install astral-uv --classic

Classic confinement is required because uv writes into your project directories and your home cache, which a strictly confined snap cannot reach. The snap is named astral-uv, but it registers uv and uvx aliases, so the commands you type afterwards are the usual ones. This package is maintained by a community contributor rather than by Astral, although it tracks upstream releases closely.

Updating and Uninstalling uv

If you installed uv with the standalone installer, update it to the latest release with:

Terminal
uv self update

That command works only for standalone installs. When uv came from pip, pipx, Homebrew, Cargo, or Snap, upgrade it through the same tool, for example pipx upgrade uv, brew upgrade uv, or sudo snap refresh astral-uv. Running uv self update against a package-manager install reports an error instead of replacing the binary behind your package manager’s back.

Remove package-manager installations through the same tool that installed them. Use pip uninstall uv, pipx uninstall uv, brew uninstall uv, cargo uninstall uv, or sudo snap remove astral-uv, depending on the method you chose.

For a standalone installation, first check which managed Python versions and tools will be removed:

Terminal
uv python list --only-installed
uv tool list

After confirming that you no longer need them, remove the cache, managed Python versions, and installed tools while uv is still on your PATH:

Terminal
uv cache clean
rm -r "$(uv python dir)"
rm -r "$(uv tool dir)"

Next, delete the binaries from the default install directory:

Terminal
rm ~/.local/bin/uv ~/.local/bin/uvx

If you set UV_INSTALL_DIR during installation, remove the binaries from that directory instead.

Project virtual environments are unaffected, since each one lives in its own project directory under .venv.

Enabling Shell Completion

uv generates its own completion scripts. Append the line for your shell, then open a new shell so it takes effect:

Terminal
echo 'eval "$(uv generate-shell-completion bash)"' >> ~/.bashrc

For Zsh, the same pattern applies to ~/.zshrc:

Terminal
echo 'eval "$(uv generate-shell-completion zsh)"' >> ~/.zshrc

Fish reads completions from its own directory:

Terminal
echo 'uv generate-shell-completion fish | source' > ~/.config/fish/completions/uv.fish

uvx is a separate binary with separate completions, and it takes the flag form rather than the subcommand form:

Terminal
echo 'eval "$(uvx --generate-shell-completion bash)"' >> ~/.bashrc

Creating a Project

uv init scaffolds a new project and initializes a Git repository inside it:

Terminal
uv init my-project
cd my-project
output
Initialized project `my-project`

By default uv creates a packaged project, with the code under src rather than in the project root:

output
my-project
├── .git
├── .gitignore
├── .python-version
├── README.md
├── pyproject.toml
└── src
    └── my_project
        └── __init__.py

The hyphen in my-project becomes an underscore in the module name, because a hyphen is not legal in a Python identifier. The .python-version file records which interpreter the project expects, and the generated .gitignore already excludes .venv and the usual build artifacts.

The generated pyproject.toml carries the project metadata, an entry point, and a build backend:

pyproject.tomltoml
[project]
name = "my-project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

[project.scripts]
my-project = "my_project:main"

[build-system]
requires = ["uv_build>=0.12.12,<0.13"]
build-backend = "uv_build"

The requires-python value comes from the interpreter uv found on your system, so yours may differ. If you would rather have a flat layout with a main.py in the project root and no build system, which suits small utilities and one-off scripts, pass --no-package:

Terminal
uv init --no-package my-script

Either way, uv has not created a virtual environment yet. That happens the first time you add a dependency or run a command in the project.

Adding and Removing Dependencies

To add a package, use uv add:

Terminal
uv add requests

On the first run, uv creates a .venv directory in the project root, resolves the dependency graph, writes a uv.lock file that pins every package and its transitive dependencies, and installs everything into the venv. The requests line is also added to pyproject.toml under dependencies.

Development-only dependencies go into a separate group with --dev:

Terminal
uv add --dev pytest ruff

They are recorded under a dev dependency group in pyproject.toml and are installed into the same venv, but they are excluded when your project is published or installed as a library.

Remove a dependency with uv remove:

Terminal
uv remove requests

uv updates pyproject.toml, updates uv.lock, and uninstalls the package along with any dependencies that are no longer needed.

Running Code in the Project Environment

You do not have to activate the virtual environment manually. uv run executes a command inside the project venv and syncs dependencies first if needed. Because uv init wrote a [project.scripts] entry, the project runs by name straight away:

Terminal
uv run my-project
output
Hello from my-project!

The same command covers anything else you need to run against those dependencies, such as the test suite:

Terminal
uv run pytest

To run a file rather than a script entry point, pass it through python. This is how projects created with --no-package are started, since they keep their code in a top-level main.py:

Terminal
uv run python main.py

If you prefer the classic workflow, activate the venv yourself. The project and its entry points are installed into .venv, so the same name works once the environment is active:

Terminal
source .venv/bin/activate
my-project

Either approach works, but uv run has the advantage of keeping the environment in sync with uv.lock every time you invoke it.

Syncing and Reproducing an Environment

When you clone a project that uses uv, run uv sync to install exactly the versions pinned in uv.lock:

Terminal
uv sync
output
Resolved 12 packages in 5ms
Installed 12 packages in 110ms

uv sync uses the versions recorded in uv.lock for the current platform and Python version. The lockfile is portable across systems, but platform markers can select different packages on Linux, macOS, or Windows. This is the command to run in CI, in Docker builds, and on every fresh checkout.

If pyproject.toml changes but the lockfile is out of date, refresh the lock with:

Terminal
uv lock

Use uv lock --upgrade to bump every dependency to the latest version allowed by the version constraints in pyproject.toml, and uv lock --upgrade-package requests to bump a single package.

Working with Virtual Environments Directly

uv can also be used as a faster drop-in for virtualenv. To create a standalone venv in the current directory:

Terminal
uv venv
output
Using Python 3.12.8
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate

Specify a Python version with --python:

Terminal
uv venv --python 3.11

Specify a different path:

Terminal
uv venv /tmp/myenv

Once the venv exists, use uv pip as a fast replacement for the regular pip commands:

Terminal
uv pip install requests
uv pip install -r requirements.txt
uv pip freeze
uv pip list

The uv pip interface is intentionally compatible with pip, which makes it easy to adopt uv in existing projects without restructuring them as pyproject.toml-based projects.

For a refresher on classic virtual environments, see the guide on Python virtual environments .

Installing and Managing Python Versions

uv can download and manage Python interpreters without touching your system Python. List the versions available and installed:

Terminal
uv python list

Install a specific version:

Terminal
uv python install 3.12
Terminal
uv python install 3.11 3.13

The downloaded interpreters live under ~/.local/share/uv/python/ and are independent of the system package manager, so nothing here disturbs the Python that apt or dnf installed. To tie a project to one of them, use uv python pin, which writes the version into the .python-version file and prints the change it made:

Terminal
uv python pin 3.13

You can also pass --python when creating a virtual environment:

Terminal
uv venv --python 3.13

uv python install puts versioned executables such as python3.12 in ~/.local/bin. If that directory is missing from your PATH, uv can add it for you:

Terminal
uv python update-shell

Interpreters you no longer need come off just as easily:

Terminal
uv python uninstall 3.11

When uv run starts a project, it reads .python-version and the requires-python constraint in pyproject.toml, then picks or downloads a matching interpreter automatically. To confirm which interpreter a shell is currently using, see the guide on checking your Python version .

Installing CLI Tools

uv ships with a tool manager similar to pipx. It installs Python-based CLIs in isolated environments so they do not pollute your system Python:

Terminal
uv tool install ruff

The ruff binary is now on your PATH. List, upgrade, or remove tools:

Terminal
uv tool list
uv tool upgrade ruff
uv tool uninstall ruff

To run a tool without installing it permanently, use uvx (an alias for uv tool run):

Terminal
uvx ruff check .

uvx downloads the tool into a cached environment, runs it, and reuses the cache on subsequent invocations. This is the fastest way to try a CLI without making it permanent.

Running Single-File Scripts

A Python script can declare its dependencies inline with a PEP 723 comment block. uv run reads that block and sets up an ephemeral environment before executing the script:

script.pypy
# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "requests",
# ]
# ///

import requests

response = requests.get("https://api.github.com")
print(response.status_code)

Run the script with:

Terminal
uv run script.py

uv creates a temporary venv, installs requests, runs the script, and keeps the environment in a cache for next time. This pattern is useful for small utilities and one-off automation where a full project is overkill.

Troubleshooting

uv: command not found after install
The installer places the binary in ~/.local/bin, which may not be on your PATH. Add it in your shell rc file: export PATH="$HOME/.local/bin:$PATH", then reload the shell. Verify with which uv.

Installation fails with “error: externally-managed-environment”
This message comes from system pip, not from uv. Use pipx install uv or the standalone installer instead. Installing Python packages into the system Python is discouraged on modern Ubuntu and Debian releases.

uv sync installs different versions than expected
Check that uv.lock is committed to your repository. Without the lockfile, uv sync falls back to resolving from pyproject.toml and may pick newer versions. Commit uv.lock to keep environments reproducible across machines.

uv cannot find a suitable Python interpreter
Run uv python install 3.12 (or whichever version your project requires) to let uv download a matching interpreter. The requires-python field in pyproject.toml tells uv which versions are acceptable.

Permission denied when installing to the system
uv does not need root. If a command prompts for sudo, the destination path is wrong. Use a project virtual environment or uv tool install for CLIs instead of writing to system directories.

FAQ

How is uv different from pip?
pip installs packages into an existing Python environment. uv is a full project manager: it creates and manages virtual environments, locks dependencies, installs Python interpreters, and runs scripts and tools. uv pip also provides a drop-in pip-compatible command for teams that want just the speed without adopting the full workflow.

Does uv replace Poetry or Hatch?
For most projects, yes. uv reads the standard pyproject.toml format used by Poetry and Hatch, manages lockfiles, and handles dependency groups. If you depend on specific features of Poetry plugins or Hatch build hooks, evaluate those needs before migrating.

Is uv compatible with existing requirements.txt workflows?
Yes. uv pip install -r requirements.txt works as a faster replacement for pip install -r, and uv pip compile generates a pinned requirements file from your unpinned input, similar to pip-compile from pip-tools.

Can I use uv with Docker?
Yes, and it is a good fit. Copy pyproject.toml and uv.lock first, run uv sync --locked --no-install-project to install the dependencies on their own layer, then copy the rest of the source and run uv sync --locked. Mounting /root/.cache/uv as a build cache lets downloaded wheels survive between builds. Prefer --locked over --frozen, because it fails the build when uv.lock is out of date with pyproject.toml instead of quietly installing a stale lockfile.

Where does uv store downloaded interpreters and package cache?
Interpreters go under ~/.local/share/uv/python/, tools installed with uv tool install go under ~/.local/share/uv/tools/, and the package cache lives in ~/.cache/uv/. All three follow the XDG base directories, so XDG_DATA_HOME and XDG_CACHE_HOME move them together. To relocate one without the others, set UV_PYTHON_INSTALL_DIR, UV_TOOL_DIR, or UV_CACHE_DIR.

Conclusion

uv rolls together the jobs that previously required pip, virtualenv, pip-tools, pipx, and pyenv into one fast binary. The speed is the headline feature, but the real payoff is a single, consistent workflow for projects, tools, scripts, and Python versions.

For related Python tooling on Linux, see the guide on installing Python on Ubuntu 26.04 and the guide on Python virtual environments .

Linuxize Weekly Newsletter

A quick weekly roundup of new tutorials, news, and tips.

About the authors

Dejan Panovski

Dejan Panovski

Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 1000+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.

View author page