Skip to main content

uv

uv: Python Environment & Package Manager

A single Rust binary that replaces pip, venv, pyenv, pipx, and poetry. It guarantees the right Python version and packages are present before running your code, so you stop managing virtual environments by hand.

Use it when: you want edit-and-run Python scripts (scrapers, automation, data jobs) without venv setup, manual activation, or a compile step.


Mental Model

uv sits between you and Python. Three modes, each storing its environment differently:

Mode When to use Where the env lives
Inline script (PEP 723) One .py file, ad-hoc scripts Ephemeral, in the global cache
Project Multi-file app, reproducible builds Visible .venv/ in the project folder
Tool install Installing a CLI globally (e.g. ruff) Isolated, like pipx

For a scraper / automation script, use inline script mode.


Install

# Linux / macOS (recommended, no existing Python needed)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windows (PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Installs the uv binary to:

  • Linux / macOS: ~/.local/bin
  • Windows: %USERPROFILE%\.local\bin

Remember this path. cron and other minimal-environment runners do not include it in PATH.

Alternatives: pipx install uv, pip install uv, brew install uv. Update later with uv self update.


Inline Scripts (PEP 723)

Declare dependencies in a comment header at the top of the file. No requirements.txt, no project, no manual venv.

# scraper.py
# /// script
# requires-python = ">=3.12"
# dependencies = ["httpx", "polars", "PyMySQL"]
# ///

import httpx, polars as pl, pymysql
# your code here

Run it:

uv run scraper.py

First run resolves, downloads, and builds a cached environment, then executes. Subsequent runs reuse the cache and start almost instantly. uv rebuilds the env only when the dependency list or Python version changes.

Add a dependency without hand-editing the header:

uv add --script scraper.py requests

Make the file directly executable:

#!/usr/bin/env -S uv run --script
chmod +x scraper.py
./scraper.py

Where the Cached venv Lives

Inline-script environments live in uv's global cache, not next to the file.

OS Default cache path
Linux $XDG_CACHE_HOME/uv or ~/.cache/uv
macOS ~/Library/Caches/uv
Windows %LOCALAPPDATA%\uv\cache

Find it on any machine:

uv cache dir
  • Folder names are hashes of the Python version, dependency versions, and script name. Not human-readable.
  • The cache is deduplicated: each package version is downloaded and built once, then shared across all scripts and projects on the machine.
  • Override the location with the UV_CACHE_DIR environment variable.

Housekeeping:

uv cache clean    # wipe entire cache
uv cache prune    # remove stale entries only
du -sh "$(uv cache dir)"   # check size (Linux/macOS)

For project mode, the env is instead a normal .venv/ in the project root, which you can inspect or delete directly.


Reproducibility (Pin Versions)

Unpinned dependencies will silently upgrade when a new release drops. For anything unattended, lock the versions:

uv lock --script scraper.py   # creates scraper.py.lock

Once locked, uv run, uv add --script, and uv tree --script reuse the pinned versions.

For a collection of related scripts you want to rebuild on another machine, use a project instead: a folder with pyproject.toml + uv.lock, then uv sync on the new machine reproduces the exact Python version and dependencies.


Running in Cron

It works. Two things bite people, both about the environment, not uv.

1. PATH

cron uses a stripped-down PATH (typically /usr/bin:/bin) that excludes ~/.local/bin, so a bare uv fails with "command not found". Use the absolute path (find it with which uv).

2. HOME / cache

uv needs HOME set to locate ~/.cache/uv. User crontabs usually set this correctly. System crontabs, containers, and service accounts may not, so set it explicitly.

# crontab -e
HOME=/home/youruser
UV_CACHE_DIR=/home/youruser/.cache/uv

*/15 * * * * /home/youruser/.local/bin/uv run /home/youruser/scrapers/scraper.py >> /home/youruser/logs/scraper.log 2>&1

Always redirect stdout and stderr to a log file, or failures vanish silently.

Cron checklist

  • Pin dependencies (uv lock --script) so a 3am package update cannot break the job.
  • Warm the cache once by hand; the first run does the slow install work.
  • Test the exact command in a minimal shell before scheduling:
    env -i HOME=/home/youruser /home/youruser/.local/bin/uv run scraper.py
    

Command Cheat Sheet

Command Does
uv run script.py Run a script (project or inline)
uv add --script script.py pkg Add a dependency to an inline script
uv lock --script script.py Pin versions for an inline script
uv init Create a new project
uv add pkg Add a dependency to a project
uv sync Rebuild a project env from the lockfile
uv python install 3.12 Install a specific Python version
uv tool install ruff Install a CLI tool globally (pipx-style)
uv cache dir Show the cache location
uv cache clean Wipe the cache
uv self update Update uv itself

Gotchas

  • MariaDB driver: avoid the official mariadb PyPI package; it builds a C extension and needs MariaDB Connector/C installed system-wide. Use PyMySQL (pure Python, no compile) or SQLAlchemy + PyMySQL instead.
  • Unpinned scripts upgrade silently. Lock anything that must not break unattended.
  • cron PATH/HOME as above.
  • Cache and Python env should be on the same filesystem for fast hard-linking; otherwise uv falls back to slow copies.

Sources

Last reviewed: June 2026. Verify install URLs and cache behavior against the current docs if uv has had a major version bump.