uv
Whatuv: 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:
| Mode | When to use | Where the |
|---|---|---|
| Inline script (PEP 723) | One .py |
Ephemeral, in the global |
| Project | Multi-file
|
Visible .venv/ in the project |
| Tool |
Installing 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 - Windows:
%USERPROFILE%\.local\bin
Remember
thatthispath;path. cron and other minimal-environment runners do not include itmattersinforPATH.cron
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:
uv add --script scraper.py requests. To make it reproducible:uv lock --script scraper.pyproducesscraper.py.lock.
You can also makeMake the file directly executable.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_DIRenvironment 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.
#(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/ thecachecache.
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 not, HOMEis 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) soversionsaare3amfrozen.packageSecond,update cannot break theveryjob. - Warm the cache once by hand; the first
cron executionrun does the slow installwork,work. -
can take seconds to a minute; warm the cache by running it once manually before trusting the schedule.Test
before scheduling: runthe exactcroncommandline by hand fromin 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 |
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
mariadbPyPI package; it builds a C extension and needs MariaDB Connector/C installed system-wide. Use PyMySQL (pure Python, no compile) orHOMESQLAlchemyproblems+beforePyMySQL instead. - Unpinned scripts upgrade silently. Lock anything that must not break unattended.
- cron
does.PATH/HOMESources:as above. - Cache and Python env should be on the same filesystem for fast hard-linking; otherwise uv falls back to slow copies.
Sources
- uv official
docsdocs:(https://docs.astral.sh/uv)uvfor(storage, cache,storage,runningandscripts, CLIbehavior;reference) - PEP 723 (inline script metadata): https://peps.python.org/pep-0723/
uv-cacheman pagefor(platformpaths;cachethisdavej.compaths)
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.