Getting Started with uv: A Practical Guide to Python Environments from Scratch
Say goodbye to wrestling with Python versions, virtual environments, and slow dependency resolvers. Here is a step-by-step walkthrough for configuring uv, structuring projects, and running Python code seamlessly.
If you have spent any significant amount of time in the Python ecosystem, you have likely run into the classic environment dilemma: a script runs flawlessly on your laptop, but falls apart on a colleague’s workstation because of mismatched minor versions or conflicting package requirements. Over the years, the community responded with an assortment of dedicated tools—pyenv for managing Python runtimes, virtualenv or venv for isolated environments, and pip-tools or poetry for locking dependencies. While these tools solved real problems, juggling three or four separate utilities across multiple projects often feels fragmented and needlessly slow.
Enter uv, an extremely fast Python package and project manager developed by Astral (the team behind Ruff). Written in Rust, uv brings these disjointed workflows together into a single, cohesive command-line interface.
In this guide, we will walk through setting up uv from scratch, managing Python versions without touching your system PATH, initializing a standardized project, managing dependencies, and running code reliably.
1. What Exactly is uv?
At its core, uv is an all-in-one toolchain for Python development. Rather than replacing Python itself, uv manages everything around your Python workflow:
- Python Runtime Management: Automatically downloads, installs, and switches between specific Python versions (e.g., 3.10, 3.12, 3.13) without needing external tools like
pyenv. - Ultra-Fast Dependency Resolution: Resolves and installs packages orders of magnitude faster than conventional
pip, caching wheels globally to save both time and disk space. - Automatic Virtual Environments: Creates isolated environments per project and synchronizes them transparently whenever your dependency list changes.
- Ephemeral Script Execution: Runs scripts directly through
uv runwithout requiring you to manually activate or deactivate virtual environments in your shell. - Deterministic Lockfiles: Generates a cross-platform
uv.lockfile so your teammates and CI pipelines install identical dependency trees down to the byte.
For detailed design notes and benchmark figures, check out the official uv documentation.
2. Prerequisites
Before we start installing anything, ensure you have:
- A computer running Windows 10/11, macOS, or modern Linux.
- An active internet connection to pull runtimes and packages.
- Access to a terminal: PowerShell on Windows, or Terminal (zsh/bash) on macOS and Linux.
- Your favorite text editor or IDE (Visual Studio Code, PyCharm, or Neovim).
Do I need Python pre-installed on my machine?
No. One of the cleanest advantages of uv is that it can download and manage standalone, pre-built Python binaries directly from Astral’s upstream builds. If you already have Python installed, uv will happily detect it, but starting with a clean machine is completely fine.
3. Installing uv on Your System
A. Windows (PowerShell)
On modern Windows, the most straightforward approach is using the Windows Package Manager (winget):
winget install --id astral-sh.uv -e
If winget is not configured on your system, you can use the official PowerShell standalone installer:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
Once the installer finishes, close and reopen PowerShell so your updated environment variables take effect.
B. macOS
If you use Homebrew:
brew install uv
Alternatively, use the official shell script:
curl -LsSf https://astral.sh/uv/install.sh | sh
C. Linux
Run the official standalone installer:
curl -LsSf https://astral.sh/uv/install.sh | sh
(If curl is missing, install it via your distribution’s package manager, e.g., sudo apt install curl).
For full installation flags and alternative package managers, refer to the uv installation guide.
4. Verifying the Installation
Open a fresh terminal window and check whether the binary is in your executable path:
uv --version
You should see output indicating the installed version:
uv 0.5.x (or newer)
To view available top-level commands, run:
uv --help
Troubleshooting: Command Not Recognized?
If your terminal reports thatuvcannot be found, close all open terminal windows and relaunch your shell. On Windows, verify that%USERPROFILE%\.cargo\binor%LOCALAPPDATA%\uvis included in your userPATHenvironment variable.
5. Installing Python Versions Through uv
Now let’s grab a clean Python runtime. We will install Python 3.12 for this tutorial:
uv python install 3.12
uv automatically fetches a portable, optimized Python 3.12 distribution. To inspect what versions are installed or available upstream, run:
uv python list
This command prints a table of local Python installations alongside remote versions ready for download. You never have to worry about polluting your machine’s global operating system interpreter again.
6. Creating Your First Project
Let’s initialize a new project called belajar-uv (or any name you prefer) locked to Python 3.12:
uv init --python 3.12 belajar-uv
Navigate into your newly created directory:
cd belajar-uv
Let’s inspect the files uv produced:
belajar-uv/
├── .git/
├── .gitignore
├── .python-version
├── README.md
├── pyproject.toml
└── src/
└── belajar_uv/
└── __init__.py
Here is a breakdown of what each generated file and directory does:
| File or Directory | Role / Layer | Functional Purpose |
|---|---|---|
pyproject.toml | Project Manifest | Stores project metadata, required Python runtime constraints, and declared dependencies. |
.python-version | Version Pin | Pins the default Python release intended for this workspace. |
src/belajar_uv/ | Application Package | Houses the importable application source code using the modern src layout. |
.venv/ | Environment Sandbox | The isolated virtual environment containing the Python interpreter and installed packages. |
uv.lock | Deterministic Lockfile | Tracks exact package versions and cryptographic hashes across your entire dependency graph. |
README.md | Documentation | Provides human-readable overview and operational instructions for the project. |
.gitignore | Version Control | Ensures machine-specific state (like .venv/ and caches) remains excluded from Git. |
7. Running Code Without Manual Environment Activation
Before writing custom code, let’s test the default application entry point generated by the template:
uv run belajar-uv
Your terminal should output:
Hello from belajar-uv!
What happened behind the scenes?
When you ran uv run, uv performed several tasks in milliseconds:
- It inspected
pyproject.tomland.python-version. - It ensured a compatible
.venvexisted with the right Python interpreter. - It resolved any required internal packages and executed the application inside that isolated environment.
You did not have to run python -m venv .venv, navigate into Scripts/, or source an activation script.
8. Adding Dependencies to Your Project
Most real-world scripts rely on third-party libraries. Suppose our project needs requests for making HTTP calls.
Add it to the project using uv add:
uv add requests
Why uv add instead of pip install requests?
Running bare pip install only puts files into your current active virtualenv without recording them in your project manifesto. If you push that code to Git, the next developer has no idea which libraries are needed.
In contrast, uv add:
- Adds
requestsdirectly underdependenciesinpyproject.toml. - Computes the entire transitive dependency tree (e.g.,
urllib3,certifi,idna,charset-normalizer). - Updates
uv.lockwith cryptographic hashes. - Synchronizes your local
.venvautomatically.
9. Writing and Executing Custom Code
Now let’s write a simple script that imports requests and verifies our environment setup.
In the root of your project directory (belajar-uv/), create a file named main.py:
import requests
def main() -> None:
print("Python script successfully executed with uv!")
print(f"Installed requests version: {requests.__version__}")
if __name__ == "__main__":
main()
Your directory structure will now look like this:
belajar-uv/
├── .python-version
├── .venv/
├── main.py
├── pyproject.toml
├── src/
│ └── belajar_uv/
│ └── __init__.py
└── uv.lock
Run your new script using uv run:
uv run main.py
Output:
Python script successfully executed with uv!
Installed requests version: 2.32.x
Because uv add requests was run earlier, uv immediately recognizes requests as a registered project dependency and launches Python with full access to it.
10. Key Commands in the Daily uv Workflow
Here is a quick reference table of essential uv commands:
| Command | Purpose |
|---|---|
uv --version | Checks the currently installed uv version |
uv python install <ver> | Installs a specific Python version (e.g., 3.12, 3.11) |
uv python list | Lists all available and locally installed Python runtimes |
uv init <project-name> | Scaffolds a new Python project structure |
uv add <package> | Declares a dependency and updates pyproject.toml & uv.lock |
uv remove <package> | Removes a dependency from the project configuration |
uv sync | Reconciles your virtual environment with declared lockfile dependencies |
uv run <script.py> | Runs a script or binary within the project environment |
uv lock | Regenerates or updates uv.lock without altering .venv |
Understanding add, sync, and run
uv addchanges your project’s intent by adding new requirements.uv syncensures your filesystem (.venv) accurately mirrors your project’s declared state.uv runis an operational command: it validates that the environment is up to date, then runs your command.
11. Do You Ever Need to Manually Activate the Virtual Environment?
Generally, no. Using uv run for all commands is faster and prevents common mistakes (like running scripts in the wrong terminal tab where the environment was never activated).
However, if you are using an IDE extension or need interactive terminal access, you can still activate the virtual environment manually whenever you want:
On Windows (PowerShell):
.\.venv\Scripts\Activate.ps1
On macOS & Linux:
source .venv/bin/activate
Once activated, your terminal prompt will display (belajar-uv), and regular python main.py calls will resolve against the virtual environment’s interpreter.
12. Sharing and Reproducing Projects Across Machines
One of the biggest wins of moving to uv is deterministic reproducibility. When sharing your project via GitHub or transferring it to a new server:
Files to Commit to Git:
pyproject.tomluv.lock.python-version- Application source code (
src/,main.py, tests, etc.)
Files to NEVER Commit (.gitignore):
.venv/(virtual environments are machine-specific and should never be checked into version control)
Setting Up on the Target Machine:
On any other computer with uv installed, reproducing the entire workspace is a one-liner:
# Clone the repository and enter directory
git clone https://github.com/your-username/belajar-uv.git
cd belajar-uv
# Synchronize dependencies exactly as pinned in uv.lock
uv sync
# Run your code
uv run main.py
uv reads uv.lock, automatically fetches Python 3.12 if it is missing, installs the exact package versions, and has you running in seconds.
13. Common Troubleshooting & Practical Tips
1. uv is not recognized as an internal or external command
Close and reopen your terminal. If the issue persists, inspect your system environment variables and verify that uv’s install folder is added to PATH.
2. PowerShell blocks the virtualenv activation script
If running Activate.ps1 produces an ExecutionPolicy error, do not lower your entire system security policy. Simply use uv run main.py instead—it bypasses shell script execution entirely.
3. ModuleNotFoundError: No module named '...'
You likely imported a library in your Python code that has not been recorded in pyproject.toml. Run uv add <package-name>, then re-run with uv run.
4. The first installation seems slightly slower
During initial execution, uv downloads the standalone Python binary and package wheels over your network. Subsequent runs use Astral’s global content-addressable cache, making environment creation virtually instantaneous.
Summary & Next Steps
Setting up Python environments used to be a frustrating rite of passage for newcomers and a recurring annoyance for seasoned engineers. By unifying Python version management, dependency resolution, lockfile synchronization, and environment execution into a single, blazing-fast tool, uv significantly raises the standard for developer experience.
With the foundational workflow covered here, you are ready to expand into more complex setups—such as running FastAPI microservices, orchestrating data pipelines with pandas and polars, or managing PyTorch environments for machine learning models.
References & Further Reading
Related Articles
More writings on Software Engineering and related technologies.
From a Python Script to a Real Project: A Practical Workflow with uv
Learn how to transform a scrappy Python script into a structured, maintainable, and testable CLI application using uv. We build Text Inspector from the ground up with modular code, CLI entry points, and automated pytest suites.
Understanding pyproject.toml, uv.lock, and .venv: How uv Manages Python Projects
Demystifying the foundational triad that makes modern Python projects structured, reproducible, and portable across machines. Learn how configuration, lockfiles, and virtual environments work together under the hood.