How to lock uv script dependencies for reproducible execution
A self-contained script with PEP 723 inline metadata declares loose dependency ranges like requests<3. Two machines running it a week apart can resolve different versions. To make a script reproducible, lock those ranges to exact versions with a lockfile, the same way that a project pins its dependencies in uv.lock.
uv locks a single-file script without a surrounding project. This guide shows how to generate a script-adjacent lockfile and enforce it in shared automation.
Prerequisites
- uv installed, version 0.11.4 or newer (run
uv self updateto upgrade). Earlier versions accept--lockedfor scripts but do not enforce it. - A script with PEP 723 inline metadata. If you don’t have one, scaffold it with
uv init --script report.py --python 3.12and add dependencies withuv add --script report.py requests rich.
Lock the script’s dependencies
Run uv lock with the --script flag pointed at your script:
uv lock --script report.pyuv resolves the inline metadata and writes a lockfile named after the script:
$ uv lock --script report.py
Resolved 9 packages in 147ms
$ ls report.py.lock
report.py.lock
The lockfile is report.py.lock, sitting next to report.py. It pins every direct and transitive dependency to an exact version with hashes. That makes it the script-level equivalent of a project’s root-level uv.lock. Commit report.py.lock alongside the script so everyone who runs it resolves the same versions.
Run the script from the lockfile
Once the lockfile exists, uv run --script uses it automatically. No extra flag is needed for the everyday case:
$ uv run --script report.py
Installed 9 packages in 36ms
Hello from report.py!
uv reads report.py.lock and runs the script from the pinned versions.
Enforce the lockfile in CI with --locked
In automation, you want the run to fail when the lockfile no longer matches the script rather than silently re-resolving. Pass --locked:
uv run --locked --script report.pyIf the inline metadata has changed since the last lock (someone added a dependency without re-locking), uv refuses to proceed:
$ uv run --locked --script report.py
error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`.
The command exits non-zero, so the pipeline stops. Add --locked to the shebang to make every direct invocation strict:
#!/usr/bin/env -S uv run --locked --scriptTip
Use --frozen instead of --locked to install strictly from the existing lockfile without checking it against the inline metadata. This skips resolution entirely, which is useful on an offline runner or when you want the fastest possible cold start.
Update the lockfile when dependencies change
Adding a dependency refreshes the lockfile in the same step, once the lockfile exists:
uv add --script report.py httpxTo refresh pinned versions for a security update without changing the declared ranges, upgrade and re-lock:
uv lock --upgrade --script report.pyBump a single package while holding the rest in place:
uv lock --upgrade-package requests --script report.pyCommit the updated report.py.lock with the change.
Export the locked set for non-uv consumers
When a downstream tool expects a requirements.txt, export the pinned dependencies with hashes from the script lockfile:
uv export --script report.py -o requirements.txtThe output is a fully pinned, hash-checked requirements file that pip install -r requirements.txt --require-hashes can consume on a machine without uv.
Learn more
- How to write self-contained Python scripts using PEP 723 inline metadata covers declaring a script’s dependencies before you lock them.
- How to pin dependencies with hashes in uv explains hash verification for supply-chain safety.
- uv: Locking and syncing documents the
--lockedand--frozenflags in full.