Skip to main content

uv

What

uv: uvPython actuallyEnvironment is

& Package Manager

uv is aA single Rust binary that doesreplaces thepip, jobvenv, ofpyenv, pip, venv, pyenv, pipx,pipx, and poetry. combined. Mental model: it sits between you and Python, and whenever you run a script itIt guarantees the right Python version and the right packages are present first,before thenrunning runsyour it.code, Youso you stop thinkingmanaging aboutvirtual environments atby all.hand.

ItUse operatesit inwhen: threeyou 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, andeach storing its environment differently:

wherethevenvlives:

— onedependency header. No project, no folder. The venv is ephemeral and livesThisisyour scraper use case.
  • a fresh environment. The reason for the global cache is deduplication: a package is downloaded and built only once, and that artifact is reused across all projects and environments on the same machine that need that exact version. So ten scripts using polars share one copy on disk.

    Housekeeping when it grows: uv cache clean wipes it, uv cache prune trims stale entries.

    For a project, by contrast, the venv is the visible

    anordinaryvirtualenvcan
    ModeWhen to useWhere the modeenv determineslives
    Inline script (PEP 723) One .py filefile, withad-hoc ascripts Ephemeral, in the global cache.cache
    Project Multi-file aapp, folder with pyproject.toml and uv.lock. The venv is a normal .venv/ directory inside the project. Reproducible across machines via uv sync.
  • Tool install — uv tool install ruff installs a CLI globally, isolated, like pipx.
  • Where the cached venv is

    For inline scripts, the environment lives in uv's global cache, not next to your file. The default cache location is $XDG_CACHE_HOME/uv or ~/.cache/uv on Linux, ~/Library/Caches/uv on macOS, and %LOCALAPPDATA%\uv\cache on Windows. Override it with UV_CACHE_DIR.

    Find it on any machine with:

    uv cache dir
    

    The folder names are not human-readable. The virtual environment folder names are generated from a hash of the Python version and the package dependency versions, plus the script name. Change a dependency version or rename the script and uvreproducible builds

    Visible .venv/ in the project root,folder
    Tool youinstall Installing inspecta orCLI delete.globally (e.g. ruff)Isolated, like pipx

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


    How to installInstall

    The standalone installer is the recommended route (no existing Python needed, uv can fetch Python itself):

    # 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"
    

    This dropsInstalls the uv binary into:

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

    Remember thatthis path;path. cron and other minimal-environment runners do not include it mattersin forPATH.

    cron
    below.

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


    HowInline toScripts run(PEP 723)

    InlineDeclare script,dependencies in a comment header at the wholetop workflow: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 thea cached env,environment, then executes. Subsequent runs reuse the cache and start almost instantly. Touv addrebuilds the env only when the dependency list or Python version changes.

    Add a dependency without hand-editing the headerheader:

    by hand:
    uv add --script scraper.py requests
    . To make it reproducible: uv lock --script scraper.py produces scraper.py.lock.

    You can also makeMake the file directly executable.executable:

    Add
    #!/usr/bin/env -S uv run --script
    
    as the first line, then
    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.

    OSDefault 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, ituv 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 ./scraper.pypyproject.toml. + uv.lock, then uv sync on the new machine reproduces the exact Python version and dependencies.


    Running in Cron

    Yes,It itworks. works, but twoTwo things bite people, both about the environment, not uv itself.uv.

    1. PATH.PATH

    cron runs withuses a stripped-down environment.PATH Its default PATH is (typically just /usr/bin:/bin,) whichthat does not includeexcludes ~/.local/bin, so a bare uv in a crontab fails with "command not found."found". Fix: useUse the absolute path.

    path
    #(find crontab -e
    */15 * * * * /home/youruser/.local/bin/uv run /home/youruser/scrapers/scraper.py >> /home/youruser/logs/scraper.log 2>&1
    

    Find the exact pathit with which uv. Always redirect stdout and stderr to a log file, otherwise failures vanish silently (cron emails them to a local mailbox you will never read)).

    2. HOME and/ thecache

    cache. 

    uv needs HOME set so it canto locate ~/.cache/uv. User crontabs normallyusually set HOME correctly, so this usuallycorrectly. justSystem works.crontabs, Ifcontainers, you run from a system crontab, a container, or aand service accountaccounts wheremay HOMEnot, is unset or different,so set it explicitly or pin the cache: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
    

    TwoAlways practicalredirect cautions for unattended runs. First, uv run will silently re-resolvestdout and pullstderr new package versions if your header is unpinned andto a releaselog drops; for a job that must not break at 3am, use a locked projectfile, or failures vanish silently.

    Cron checklist

    •  Pin dependencies (uv lock --script) so versionsa are3am frozen.package Second,update cannot break the veryjob.
    •  Warm the cache once by hand; the first cron executionrun does the slow install work,work.
    • which
    • can take seconds to a minute; warm the cache by running it once manually before trusting the schedule.

      Test before scheduling: run the exact cron command line by hand fromin a minimal shell (before scheduling:

      env -i HOME=/home/youruser /home/youruser/.local/bin/uv run scraper.py
      )

    Command Cheat Sheet

    CommandDoes
    uv run script.pyRun a script (project or inline)
    uv add --script script.py pkgAdd a dependency to catchan PATHinline script
    uv lock --script script.pyPin versions for an inline script
    uv initCreate a new project
    uv add pkgAdd a dependency to a project
    uv syncRebuild a project env from the lockfile
    uv python install 3.12Install a specific Python version
    uv tool install ruffInstall a CLI tool globally (pipx-style)
    uv cache dirShow the cache location
    uv cache cleanWipe the cache
    uv self updateUpdate 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 HOMESQLAlchemy problems+ beforePyMySQL instead.
    • Unpinned scripts upgrade silently. Lock anything that must not break unattended.
    • cron does.

      PATH/HOME

      Sources: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 cache-hashingcurrent detail.docs Cronif PATH/HOMEuv behaviorhas ishad standarda Unix,major notversion uv-specific.bump.