Skip to content
← All Python articles

Python explainer

Install the Same Versions Everywhere with a Lockfile

About 5 min to read. Allow extra time to work through examples.

A range such as httpx>=0.27 can install a different version next month. Record the versions you tested in a lockfile, make CI and deployment use it, and see what hashes and pip freeze can and cannot do.

This article uses Python. You can make it your practice mode to browse related content across the site.

Your pyproject.toml asks for httpx>=0.27. Say a fresh install today picks 0.28.1. Next month a new release comes out, a fresh install in CI picks it, and a test fails although nobody changed your code. The range allows both versions, so both installs were correct.

A lockfile fixes this. It records the exact version of every package an install used, including the dependencies of your dependencies. Later installs read the lockfile instead of choosing again, so CI, your laptop and production get the same versions.

Install from the lockfile everywhere

File or state What it describes
pyproject.toml Your direct dependencies and the ranges they may use
Lockfile The exact versions chosen for every package, and where each comes from
Installed environment The packages actually present when your program runs

A lockfile helps only if every install uses it. Check that CI and deployment both install from it, and that neither one chooses versions again.

An application deploys the locked versions it tested. A library is different. It can keep a lockfile so its own tests are stable, but its users install it from its ranges and never see the lockfile. So a library also needs tests across its ranges, for example against the oldest and the newest versions it allows.

uv.lock covers every platform

uv.lock is uv's lockfile. One file covers every Python version and platform the project supports. Where they need different packages, the lockfile records each choice with a marker, such as sys_platform == 'win32'. uv installs the choice that matches the machine. So a Windows install and a Linux install can get different packages from the same file.

Commit uv.lock, and change it only through uv. uv lock updates it after you edit pyproject.toml, and uv lock --upgrade-package httpx moves one package to its newest allowed version. Review a lockfile change in a pull request as you would review code. In CI, run uv sync --locked, as in the uv article, so the job fails when the lockfile is out of date. See uv's resolution documentation.

pylock.toml is the standard lockfile format from PEP 751, which different tools can share. Its name is pylock.toml, or a named form such as pylock.dev.toml. Check that both the tool that writes it and the tool that installs from it support the format. uv export --format pylock.toml writes one from uv.lock. pip lock writes one too. pip marks that command as experimental, and its output is valid only for the Python version and platform it ran on. See the pylock specification.

Check your understanding Why is the output of pip freeze not a lockfile? Show answer

pip freeze lists every package in the current environment. It does not read your project's dependencies, so it can include packages that someone installed by hand and leave out packages that another platform needs. By default it records no hashes either. Use it to describe one environment, not to reproduce your project's install somewhere else. See the freeze documentation.

Hashes check the bytes you download

A lockfile can store a hash of each package file. The installer computes the hash of the file it downloads and rejects the file if the two differ. That catches a file that changed after you locked it. It cannot tell you that the file you locked was safe, so review new versions when the lockfile changes.

pip checks hashes from a requirements file:

python3.14 -m pip install --require-hashes -r requirements.txt

In this mode, pin every requirement with ==, and give every package a hash, including the dependencies of your dependencies. One --hash option in the file turns the mode on by itself, but the flag makes it explicit in CI. Generate the file with a tool, such as uv export --format requirements.txt, which includes hashes by default. See pip's secure installs guide.

Check your understanding Production runs a dependency version that your tests never saw. What should you check? Show answer

Follow the install commands through CI and deployment. Did one of them update the lockfile, or skip it? Did they choose different extras, groups, Python versions or platforms? Did a later step install more packages on top? A requirements file exported from the lockfile is fine, so find out which command wrote each file, whatever its name. Then compare the package versions installed in each environment.

step 31 of 41

Modules, Packaging and Tooling

Up next Turn a Function into a Command