To keep Python environments aligned, pin every package to an exact version, including transitive dependencies, and install from that file everywhere. Add hash checking when you want proof that the downloaded files are the ones you reviewed. Pins fix the version; hashes fix the content.
Three levels of repeatable
The pip documentation walks through increasingly strict definitions of repeatable installs. In plain terms:
| Level | What you do | What it guards against |
|---|---|---|
| Pinned versions | Use the equals operator for each package | Surprise upgrades when a new release appears |
| Hash checking | Add a hash for each downloaded file | A changed file under the same version, or a compromised index |
| Wheelhouse | Build all dependencies into one directory or archive | Index outages and repeated compilation |
Each level costs more effort and buys more certainty.
Pins and freeze
Pinning means writing SomePackage == 1.2.3 rather than a loose range. The docs note that pip freeze can generate a requirements file pinning not only top-level packages but all transitive ones. Installing with no-deps adds a safeguard, because pip then installs nothing that is not explicitly listed.
Pinning is easy and works across operating systems and architectures. The docs are candid about its limit: it trusts the place you fetch from, and relies on that place not letting a package change without a version bump. They note PyPI protects against that.
Hashes
Hash checking mode adds a sha256 value per file. The docs say it protects against a compromise of PyPI or the HTTPS certificate chain, and also guards against a package changing without its version changing on indexes that allow it. They call it a good fit for automated server deployments.
The catch is maintenance. Every upgrade means regenerating hashes. Tools such as pip-tools build on pip to generate these files, and the docs point to it for that workflow.
A rule set for three environments
- Keep one pinned requirements file per application, generated from your top-level list.
- Install from it in dev containers, CI, staging, and production. Do not run an unpinned install anywhere that ships.
- Regenerate it on a schedule, in a pull request, so changes are reviewed and tested before they reach prod.
- For production deploys, use hash checking if your build process can regenerate hashes cleanly.
- Compare the output of pip freeze in each environment against the file. Any difference is drift.
Python version itself can drift, too. See runtime version drift for that.
Try it yourself
Click through it at your own pace, or open it full screen.
Look for how packages and versions are listed per project, the same view you would compare between environments.
What Safeguard does here
Safeguard scans Python dependency files, builds an SBOM, and keeps monitoring it for new advisories, which gives you a record to compare across environments. Try the free scan at gold.safeguard.sh/scan.
Quick check
Does pip freeze capture drift by itself?
It records what is installed in one environment. You still need to run it in each environment and compare the results.
Are hashes required?
No. Pins are the baseline. Hashes are an extra layer for deployments where integrity matters most.
Sources
Last reviewed: 19 October 2026.