How to Install and Use uv: Fast Python Package Manager

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
| Command | Description |
|---|---|
uv init my-project | Create a new project with pyproject.toml |
uv add requests | Add a dependency and update the lockfile |
uv add --dev pytest | Add a development dependency |
uv remove requests | Remove a dependency |
uv sync | Install dependencies from the lockfile |
uv lock | Update the lockfile |
uv run script.py | Run a command in the project environment |
uv venv | Create a virtual environment |
uv pip install requests | Pip-compatible install interface |
uv python install 3.12 | Install a specific Python version |
uv python list | List installed and available Python versions |
uv python pin 3.13 | Pin the project to a Python version |
uv tool install ruff | Install a CLI tool globally |
uvx ruff check . | Run a tool without installing it permanently |
uv self update | Update uv to the latest release |
uv cache clean | Clear 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:
curl -LsSf https://astral.sh/uv/install.sh | shIf curl is not installed, wget does the same job:
wget -qO- https://astral.sh/uv/install.sh | shThe 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:
uv --versionuv 0.12.12To 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:
curl -LsSf https://astral.sh/uv/0.12.12/install.sh | shThe 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:
curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="$HOME/bin" UV_NO_MODIFY_PATH=1 shBecause 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:
pip install uvpipx install uvbrew install uvOn 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:
cargo install --locked uvuv 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:
sudo snap install astral-uv --classicClassic 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:
uv self updateThat 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:
uv python list --only-installed
uv tool listAfter confirming that you no longer need them, remove the cache, managed Python versions, and installed tools while uv is still on your PATH:
uv cache clean
rm -r "$(uv python dir)"
rm -r "$(uv tool dir)"Next, delete the binaries from the default install directory:
rm ~/.local/bin/uv ~/.local/bin/uvxIf 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:
echo 'eval "$(uv generate-shell-completion bash)"' >> ~/.bashrcFor Zsh, the same pattern applies to ~/.zshrc:
echo 'eval "$(uv generate-shell-completion zsh)"' >> ~/.zshrcFish reads completions from its own directory:
echo 'uv generate-shell-completion fish | source' > ~/.config/fish/completions/uv.fishuvx is a separate binary with separate completions, and it takes the flag form rather than the subcommand form:
echo 'eval "$(uvx --generate-shell-completion bash)"' >> ~/.bashrcCreating a Project
uv init scaffolds a new project and initializes a Git repository inside it:
uv init my-project
cd my-projectInitialized project `my-project`By default uv creates a packaged project, with the code under src rather than in the project root:
my-project
├── .git
├── .gitignore
├── .python-version
├── README.md
├── pyproject.toml
└── src
└── my_project
└── __init__.pyThe 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:
[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:
uv init --no-package my-scriptEither 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:
uv add requestsOn 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:
uv add --dev pytest ruffThey 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:
uv remove requestsuv 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:
uv run my-projectHello from my-project!The same command covers anything else you need to run against those dependencies, such as the test suite:
uv run pytestTo 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:
uv run python main.pyIf 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:
source .venv/bin/activate
my-projectEither 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:
uv syncResolved 12 packages in 5ms
Installed 12 packages in 110msuv 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:
uv lockUse 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:
uv venvUsing Python 3.12.8
Creating virtual environment at: .venv
Activate with: source .venv/bin/activateSpecify a Python version with --python:
uv venv --python 3.11Specify a different path:
uv venv /tmp/myenvOnce the venv exists, use uv pip as a fast replacement for the regular pip commands:
uv pip install requests
uv pip install -r requirements.txt
uv pip freeze
uv pip listThe 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:
uv python listInstall a specific version:
uv python install 3.12uv python install 3.11 3.13The 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:
uv python pin 3.13You can also pass --python when creating a virtual environment:
uv venv --python 3.13uv 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:
uv python update-shellInterpreters you no longer need come off just as easily:
uv python uninstall 3.11When 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:
uv tool install ruffThe ruff binary is now on your PATH. List, upgrade, or remove tools:
uv tool list
uv tool upgrade ruff
uv tool uninstall ruffTo run a tool without installing it permanently, use uvx (an alias for uv tool run):
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
# requires-python = ">=3.12"
# dependencies = [
# "requests",
# ]
# ///
import requests
response = requests.get("https://api.github.com")
print(response.status_code)Run the script with:
uv run script.pyuv 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 systemuv 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 .
Tags
Linuxize Weekly Newsletter
A quick weekly roundup of new tutorials, news, and tips.
About the authors

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