Safeguard
Supply Chain•

How do you make pip installs in a Dockerfile repeatable?

Pin every Python dependency and turn on pip hash-checking mode so a tampered or surprise package fails the build. Steps, limits and a Dockerfile pattern.

Safeguard Security Team
4 min read

Pin every dependency to an exact version, record a hash for each, and install in hash-checking mode so pip refuses anything that does not match. Do the install in a build stage, then copy the result forward. This makes builds repeatable and blocks swapped packages.

What hash-checking mode enforces

The pip documentation describes the mode as verifying downloads against hashes stored in a requirements file, to guard against remote tampering and network problems. It sets strict rules.

RuleMeaning
Hashes on all requirementsOne unhashed line would let a package slip in
Hashes on all dependenciesAn unlisted dependency without a hash causes an error
Pinned versionsUse the double equals pin, a URL or a path
All or nothingAdding a hash to one line turns the mode on for the file

You can also force the mode on with the require hashes option. Generate the file with a lock tool rather than by hand, since transitive dependencies are included and the list is long.

A Dockerfile pattern

FROM python:3.13-slim AS build
WORKDIR /src
COPY requirements.lock .
RUN python -m venv /opt/venv \
 && /opt/venv/bin/pip install -r requirements.lock

FROM python:3.13-slim
# COPY with the from option set to build
COPY /opt/venv /opt/venv
COPY app /app
ENV PATH="/opt/venv/bin:$PATH"
USER 10001
CMD ["python", "/app/main.py"]

Put the require hashes setting in the lock file itself or in pip configuration, so the install cannot run unchecked. Confirm the Python tags you choose are still supported.

Install your own project without dependencies

The pip docs advise installing your own project with the no deps option, because running setup scripts directly can download dependencies that skip the hash check. Install dependencies from the lock file first, then your code with no dependency resolution.

Limits

Hashes prove the file you got is the file you recorded. They do not prove the file is safe. If the package you pinned was malicious or has a known flaw, a hash happily matches it. So combine this with scanning, review of new dependencies and a delay before adopting brand new releases.

Also expect friction. Updates now need a regenerated lock file, ideally by an automated pull request. Platform specific wheels can need separate hashes per platform, so build the lock on the platform you ship, or generate it for each.

Steps

  1. Generate a lock file with versions and hashes for all dependencies.
  2. Commit it and install only from it in the Dockerfile.
  3. Use a build stage and copy the virtual environment forward.
  4. Automate lock refreshes and scan every update.
  5. Rebuild the image on a schedule for base image fixes.

Try it yourself

Click through it at your own pace, or open it full screen.

Quick check

Do hashes stop malicious packages?

They stop swaps after you pinned. They do not judge whether the pinned package was safe, so scan and review too.

Why does pip complain about a missing hash?

In hash-checking mode every requirement and dependency needs one. Regenerate the lock file so it includes them all.

Safeguard

Safeguard does SBOM and dependency scanning, and its policies and gates can run in CI so a bad image does not reach production. You can try a free scan before deciding anything.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.