How to Use uv: The Beginner's Guide (First Project, the Lockfile, Python Versions, and the pip Escape Hatch)
CodexFuse Team · 2026-10-31T03:57:27.257Z · 12 min read
Learn how to use uv: install the fast Python package manager, create your first project, add dependencies, manage the uv.lock lockfile, and pin Python versions.
How to Use uv: The Beginner's Guide (First Project, the Lockfile, Python Versions, and the pip Escape Hatch)
Learning how to use uv is the fastest way to stop juggling Python tools. If your Python workflow is a pile of pip installs, venv folders, requirements.txt files, a pyenv shim, and a prayer, uv replaces the whole stack with one command. uv is a Python package and project manager written in Rust, built by Astral (the makers of Ruff), and its own documentation describes it plainly: "an extremely fast Python package and project manager."
This uv tutorial for beginners walks the complete arc in one sitting: install uv, let it manage Python itself, create your first project, add dependencies, understand the lockfile, run your code, and pin everything so it reproduces on any machine. You will also learn the pip escape hatch for old projects and the script trick that skips projects entirely. If you spend your Python time in notebooks instead of the terminal, start with the Jupyter beginner guide — uv manages the environments those notebooks run in, and the two guides fit together.
How Do You Use uv in 30 Seconds?
You use uv by installing it, letting it fetch Python, scaffolding a project with uv init, adding packages with uv add, and running everything through uv run. The whole beginner loop looks like this:
- Install uv —
curl -LsSf https://astral.sh/uv/install.sh | shon macOS/Linux, or the PowerShell one-liner on Windows. - Get Python through uv —
uv python install 3.12downloads and manages a Python version for you; skip this and uv fetches Python automatically the first time you need it. - Create a project —
uv init hello-worldscaffoldspyproject.toml, a README, and a Python version pin. - Add dependencies —
uv add requestswrites the package intopyproject.toml, resolves exact versions intouv.lock, and installs them. - Run your code —
uv run hello-worldexecutes the project entry point;uv run example.pyruns a script. Before every run, uv checks the lockfile and the environment are in sync, so it just works. - Share it — commit
pyproject.tomlanduv.lock; the next person runsuv syncand gets your exact environment.
What Is uv, Exactly?
uv is one tool that does the jobs of five. It installs and manages Python versions (the pyenv job), creates virtual environments (the venv job), installs packages (the pip job), resolves and locks dependencies (the pip-tools/Poetry job), and runs one-off tools without installing them (the pipx job). Astral's docs break its interface into exactly these lanes: Python versions, projects, scripts, tools, and a pip-compatible layer.
The sprawl it replaces is real. A typical Python developer in 2026 juggles pip for installs, virtualenv for isolation, pyenv for Python versions, pip-tools or Poetry for locks, and pipx for CLI tools — and each one has its own config file and its own quirks. uv folds all of that into a single binary with one mental model: declare what you want, and uv makes the environment match. One honest note on the marketing: guides love quoting speed multipliers, but Astral itself just says "extremely fast" — treat any exact number you see in a headline as marketing, not a measurement you can reproduce on your machine.
How Do You Install uv?
You install uv with Astral's standalone installer — never through pip into the same environment you want uv to manage. The official installation docs list the standalone script first, and for good reason: installed this way, uv can manage Python for you from the start and update itself later with uv self update.
On macOS and Linux, run:
curl -LsSf https://astral.sh/uv/install.sh | sh
On Windows, in PowerShell:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
No curl on your system? The docs give a wget alternative: wget -qO- https://astral.sh/uv/install.sh | sh. Prefer your system package manager instead? uv ships in Homebrew's core packages (brew install uv), on PyPI (pipx install uv or pip install uv), and as a Docker image at ghcr.io/astral-sh/uv. You can also pin a specific release by putting the version in the URL, e.g. curl -LsSf https://astral.sh/uv/0.13.6/install.sh | sh.
Verify the install with uv --version. If your shell cannot find the command, the binary lives in your user executable directory (usually ~/.local/bin or ~/.cargo/bin) — open a fresh terminal or add that directory to your PATH. On Ubuntu specifically, minimal installs often lack curl entirely, so install that first; the uv installer itself never needs sudo.
How Does uv Install and Manage Python Versions?
uv can download and manage Python itself, so you do not need Python pre-installed — and you do not need pyenv. uv distinguishes managed Python (versions it installs) from system Python (everything else), and it will automatically download a Python version the first time a command needs one you do not have.
To install versions explicitly:
uv python install 3.12
That fetches the latest patch release of 3.12. Install several at once with uv python install 3.11 3.13, pin an exact patch with uv python install 3.12.3, or request a range with uv python install '>=3.8,<3.10'. uv bundles downloadable CPython and PyPy distributions for macOS, Linux, and Windows, so alternative interpreters are covered too.
Two commands keep your versions organized. uv python list shows every Python uv can see — installed and available — and uv python find locates a specific installed version. To pin a project's Python, run uv python pin 3.12 in the project directory: it writes a .python-version file, and uv then uses that version for every project operation. Add --global to set your default outside any project. When no version is requested or pinned, note that uv 0.13.0 (released October 2026) made Python 3.15 the default stable download — so a bare uv python install now lands you on 3.15.
How Do You Create Your First uv Project?
You create a project with uv init, which scaffolds everything a modern Python project needs. Run it with a name to create a new directory, or bare inside an existing one:
uv init hello-world
cd hello-world
uv creates a pyproject.toml, a README.md, a .python-version pin, a .gitignore, a git repo, and a source tree at src/hello_world/__init__.py. The default template is an application (web servers, scripts, CLIs); add --lib for a library you intend to publish to PyPI. The generated pyproject.toml declares your metadata, a [project.scripts] entry point, a requires-python floor (the docs' current example uses >=3.14), and a build system based on Astral's uv_build.
Two variations matter for beginners. If you want a plain script-style project with main.py at the top level instead of a src layout, use uv init --no-package example-app. If you want only a pyproject.toml and nothing else, use uv init --bare. One trap: if the directory already contains a pyproject.toml, uv init refuses and exits with an error — which is exactly what protects the migration workflow in the later section from clobbering your files.
Try the scaffold immediately:
uv run hello-world
You should see Hello from hello-world!. Notice what you did not do: no manual venv creation, no activation, no pip install. The first project command (uv run, uv sync, or uv lock) created the .venv directory and the uv.lock file for you.
How Do You Add and Remove Dependencies?
You add dependencies with uv add, which updates pyproject.toml, re-resolves the lockfile, and installs into your environment in one step. The basic form is all most beginners ever need:
uv add requests
Version constraints go in quotes: uv add 'requests==2.31.0'. Git dependencies work directly: uv add git+https://github.com/psf/requests. Development-only tools (test runners, linters) stay out of your runtime with the --dev flag: uv add --dev pytest ruff. And migrating an old dependency list is one command: uv add -r requirements.txt, optionally with a constraints file via -c constraints.txt.
Removing is symmetric — uv remove requests cleans the package out of both the environment and the lockfile. To upgrade one package without touching the rest of the lock, use uv lock --upgrade-package requests; the flag updates just that package to the latest compatible version and leaves everything else pinned. The docs' dependency guide is the reference for all of these, and the pattern to internalize is simple: you declare intent in pyproject.toml, uv owns the exact versions in the lockfile, and you never hand-edit either side of that contract.
What Is the uv.lock File, and Should You Commit It?
Yes — commit uv.lock, always. It is a cross-platform lockfile that records the exact resolved version of every dependency (including hashes and platform markers), so uv sync on another machine installs precisely what you tested. The docs are explicit: the lockfile is human-readable TOML, it is managed by uv, and you should not edit it manually.
Think of the two files as a contract. pyproject.toml says "my project needs requests, any recent 2.x" — broad requirements you write. uv.lock says "on every machine, install exactly requests 2.32.3 with these hashes" — exact resolutions uv computes. Your .gitignore (which uv init generates for you) already excludes .venv, because the virtual environment is disposable: anyone can rebuild it from the lockfile in seconds. Committing the lockfile is what turns "works on my machine" into "works on every machine," and it is the single habit that separates reproducible Python projects from fragile ones.
When you push, your teammates (or your future self on a new laptop) clone and run one command — covered in the sync section — and get your exact environment. This is also why the --locked flag exists for CI: it makes the build fail loudly instead of silently re-resolving when the lockfile drifts out of sync with pyproject.toml.
How Do You Run Code in a uv Project?
You run code with uv run, and you stop thinking about activation. Before every invocation, uv verifies the lockfile matches pyproject.toml and the environment matches the lockfile — syncing automatically if anything drifted — then runs your command with all dependencies at their locked versions. Run the scaffold's entry point or any script:
uv run hello-world
uv run example.py
Need to run a server or pass flags? Put the command after --: uv run -- flask run -p 3000. Want to check which version your package reports? uv version reads it from the project metadata. One nuance worth knowing: uv run does not remove packages that are in your environment but missing from the lockfile — it guarantees the locked ones are present, and leaves extras alone.
If you prefer the classic activate-then-run flow, it still exists: run uv sync once, then source .venv/bin/activate on macOS/Linux or .venv\Scripts\activate in Windows PowerShell, and work normally. But try living in uv run for a week first — most beginners never go back, because the "did I activate the right venv?" class of bug disappears entirely.
How Do You Sync a uv Project on a New Machine?
You sync with uv sync — one command rebuilds the entire environment from the lockfile. Clone the repo, run uv sync, and uv creates the .venv and installs every locked dependency, downloading Python itself if the pinned version is missing. Nothing else to configure.
In continuous integration, use uv sync --locked: it installs from the lockfile and fails the build if the lockfile is out of sync with pyproject.toml, instead of silently re-resolving. The official astral-sh/setup-uv GitHub Action installs the uv binary on runners with an optional cache, and a typical CI job is three steps — check out the code, set up Python (uv respects your .python-version file or the requires-python pin), then uv sync --locked. If your CI life still revolves around pushing code and watching pipelines, the GitHub beginner guide covers the repository side of this workflow.
Deploying the finished app is the same story one level up: the environment uv builds on your laptop is the environment you ship. Containerize it with a multi-stage Dockerfile that runs uv sync --locked at build time — the Docker beginner guide walks through that pattern — and host it on a server you control; what is VPS hosting explains the server side for beginners renting their first one.
How Do You Run Standalone Scripts with uv?
For one-off scripts, uv skips the project entirely and manages a throwaway environment per script. The quickest form declares the dependency on the command line:
uv run --with rich example.py
uv resolves rich, builds an isolated environment (cached under ~/.cache/uv so re-runs are near-instant), executes the script, and you never created a venv. Add version constraints with uv run --with 'rich>12,<13' example.py, or repeat --with for multiple packages. Running inside a project directory? --with dependencies are added on top of the project's — pass --no-project before the script name to opt out.
For scripts you will run more than once, declare the dependencies inside the script with the PEP 723 inline metadata format. Create one with uv init --script example.py --python 3.12, or attach dependencies to an existing script with:
uv add --script example.py 'requests<3' 'rich'
That writes a # /// script TOML block at the top of the file listing the dependencies (and optionally requires-python), and from then on plain uv run example.py just works — even inside a project directory, where inline-metadata scripts ignore the project's dependencies. For reproducible script runs in CI, uv lock --script example.py writes an example.py.lock file next to the script. This lane is genuinely advanced — keep it out of your first project, and reach for it when you start automating with standalone scripts.
What Is uvx, and When Do You Use It?
uvx runs a Python tool without installing it into any project — it is an alias for uv tool run. Need to lint a file with Ruff once, or format code with Black, without touching your project's dependencies?
uvx ruff check .
uv downloads Ruff into a temporary isolated environment, runs it, and your project never knows it happened. When a tool earns a permanent place in your workflow, install it user-wide with uv tool install ruff instead — then the command is available everywhere, still isolated from every project. This is the pipx lane of the old world, absorbed into uv: tools live outside projects, projects stay clean.
How Do You Migrate an Existing pip Project to uv?
You migrate in four steps, and you keep pip as an escape hatch for anything stubborn. Say you have an old project with a requirements.txt and a hand-made .venv. First, scaffold the project files in place:
uv init .
This works because the directory has no pyproject.toml yet — remember, uv init refuses to run where one already exists. Then import your dependencies, lock them, delete the old environment, and verify:
uv add -r requirements.txt
uv lock
Delete the old .venv directory and run uv run on your entry point to confirm everything resolves under uv's management. The lockfile you get is strictly better than the requirements.txt you had: it records hashes and platform markers, not just version pins.
Not ready for the full project workflow? The pip escape hatch is uv pip — a pip-compatible interface for plain environments. uv venv creates a virtual environment, then uv pip install -r requirements.txt installs into it at uv's speed, and uv pip compile requirements.in --output-file requirements.txt generates pinned requirements the pip-tools way. The docs are candid that this layer is pip-compatible, not pip-identical: the further you stray from common workflows, the more likely you are to hit a difference, so check the pip-compatibility guide before migrating anything exotic.
uv vs pip vs Poetry: What Does uv Actually Replace?
uv does not replace one tool — it replaces a toolchain, lane by lane. Here is the honest mapping:
| Old tool | What it did | The uv equivalent |
|---|---|---|
| pip | Installed packages | uv pip install (compatibility) or uv add (project way) |
| venv / virtualenv | Isolated environments | uv venv — or automatic with uv init |
| pyenv | Installed Python versions | uv python install + uv python pin |
| pip-tools | Compiled pinned requirements | uv lock (+ uv pip compile for the old format) |
| Poetry / Pipenv | Projects + lockfiles | uv init + uv add + uv.lock |
| pipx | Ran CLI tools in isolation | uvx / uv tool install |
The row beginners misunderstand most is Poetry. Poetry and uv solve the same problem — project-centric dependency management with a lockfile — so they are rivals, not complements: pick one per project. pip, by contrast, is not a rival at all; uv pip exists precisely so your existing pip muscle memory keeps working while you migrate. And pyenv users should know what they gain: with uv, the Python version travels with the project in .python-version and requires-python, instead of living in a per-user shim directory that every new machine has to reconstruct.
What Mistakes Do Beginners Make with uv?
Most uv mistakes come from applying pip-era habits to a tool with a different mental model. The common ones:
| Mistake | Why it hurts | Do this instead |
|---|---|---|
Installing uv with pip into the environment uv should manage | You create a chicken-and-egg dependency on the thing you are replacing | Use the standalone installer; let uv own Python |
Hand-editing uv.lock | Your edits get overwritten or, worse, silently desynced | Change pyproject.toml (or uv add), then let uv re-resolve |
Committing .venv to git | Thousands of files, platform-specific binaries, merge conflicts | Commit pyproject.toml + uv.lock; .venv stays ignored |
Forgetting --dev for test tools | Linters and test runners ship into production installs | uv add --dev pytest ruff keeps them out of the runtime |
Running uv init where a pyproject.toml exists | It errors out — which is protection, not a bug | That directory is already a project; use uv add there |
Assuming uv run cleans the environment | Stale packages linger and mask missing declarations | It guarantees locked packages are present; prune with a fresh uv sync when in doubt |
One more, for the editor crowd: if you write Python in VS Code, point the editor at uv's .venv interpreter once per project — the VS Code beginner guide shows where that setting lives — and the red squiggles about missing imports disappear.
Sources
Every command and behavior in this guide was verified against the following sources, all fetched on 2026-10-30:
- uv official documentation — Installation (docs.astral.sh): standalone installer commands for macOS/Linux and Windows,
wgetalternative, version-pinned URLs, PyPI/Homebrew/Docker options,uv self update. - uv official documentation — Working on projects (docs.astral.sh):
uv initfile tree,pyproject.tomlfields,.python-versionrole,uv.locksemantics,uv add/uv remove/uv lock --upgrade-package,uv runauto-sync behavior,uv sync+ activation pattern,uv build. - uv official documentation — Creating projects (docs.astral.sh): application vs library templates,
--lib/--app/--no-package/--bareflags,uv_buildbuild backend, the init-refuses-existing-pyproject.tomlrule. - uv official documentation — Running scripts (docs.astral.sh):
uv run --with,--no-project,uv init --script,uv add --script, PEP 723 inline metadata format,uv lock --scriptproducing.lockfiles. - uv official documentation — Python versions (docs.astral.sh): managed vs system Python, auto-download,
uv python installformats,uv python list/find/pin/uninstall,--globalpin, bundled CPython/PyPy distributions. - uv official documentation — Features (docs.astral.sh):
uvxas theuv tool runalias,uv tool install,uv pipas a pip-compatible interface. - astral-sh/uv release 0.13.0 notes (October 2026): Python 3.15 becomes the default stable Python for downloads when no version is requested.
- Third-party practitioner guides (fetched 2026-10-30): the legacy-project migration arc (
uv init .→uv pip install -r requirements.txt→uv lock) and the GitHub Actions pattern (astral-sh/setup-uv+uv sync --lockedfailing loudly on lockfile drift).
Frequently Asked Questions
Is uv a replacement for pip?
Yes, with a bridge. The project workflow (uv init, uv add, uv run) replaces pip, venv, pip-tools, and much of pyenv in one tool. But uv also ships uv pip, a pip-compatible command layer, so your existing pip install -r requirements.txt habits keep working while you migrate — it is a compatibility interface, not a byte-identical clone of pip.
Do I need Python installed before installing uv?
No. Install uv first with the standalone installer, then run uv python install 3.12 to fetch Python — or skip that entirely, because uv automatically downloads a Python version the first time a command needs one you do not have. One of uv's best beginner properties is that a bare machine plus the installer is a complete Python setup.
Should I commit uv.lock to git?
Yes. uv.lock records the exact resolved version of every dependency, so committing it is what makes installs reproducible across machines and in CI. Commit pyproject.toml and uv.lock; never commit .venv; and never hand-edit the lockfile — change your declared dependencies and let uv re-resolve.
Can uv work with my existing requirements.txt project?
Yes, two ways. The gentle path keeps your current layout: uv venv plus uv pip install -r requirements.txt gives you uv's speed with zero restructuring. The full migration scaffolds a project in place with uv init ., imports the list with uv add -r requirements.txt, and locks it with uv lock — then your old requirements.txt workflow is obsolete.
What is the difference between uv and uvx?
uvx is an alias for uv tool run: it executes a Python CLI tool (like Ruff or Black) in a temporary isolated environment without installing it into any project. Use uvx for one-off tool runs and uv tool install for tools you want permanently on your PATH. Project dependencies and tools stay in separate worlds, which is exactly the point.