# How to lock uv script dependencies for reproducible execution


A [self-contained script](https://pydevtools.com/handbook/how-to/how-to-write-a-self-contained-script.md) with [PEP 723](https://pydevtools.com/handbook/explanation/what-is-pep-723.md) 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](https://pydevtools.com/handbook/explanation/what-is-a-lock-file.md), the same way that a project pins its dependencies in `uv.lock`.

[uv](https://pydevtools.com/handbook/reference/uv.md) 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](https://docs.astral.sh/uv/getting-started/installation/), version 0.11.4 or newer (run `uv self update` to upgrade). Earlier versions accept `--locked` for 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.12` and add dependencies with `uv add --script report.py requests rich`.

## Lock the script's dependencies

Run `uv lock` with the `--script` flag pointed at your script:

```bash
uv lock --script report.py
```

uv resolves the inline metadata and writes a lockfile named after the script:

```console
$ 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:

```console
$ 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`:

```bash
uv run --locked --script report.py
```

If the inline metadata has changed since the last lock (someone added a dependency without re-locking), uv refuses to proceed:

```console
$ 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:

```python
#!/usr/bin/env -S uv run --locked --script
```

> [!TIP]
> 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:

```bash
uv add --script report.py httpx
```

To refresh pinned versions for a security update without changing the declared ranges, upgrade and re-lock:

```bash
uv lock --upgrade --script report.py
```

Bump a single package while holding the rest in place:

```bash
uv lock --upgrade-package requests --script report.py
```

Commit 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:

```bash
uv export --script report.py -o requirements.txt
```

The 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](https://pydevtools.com/handbook/how-to/how-to-write-a-self-contained-script.md) covers declaring a script's dependencies before you lock them.
- [How to pin dependencies with hashes in uv](https://pydevtools.com/handbook/how-to/how-to-pin-dependencies-with-hashes-in-uv.md) explains hash verification for supply-chain safety.
- [uv: Locking and syncing](https://docs.astral.sh/uv/concepts/projects/sync/) documents the `--locked` and `--frozen` flags in full.
