Basic

Pinning a dependency floor

A pin is a statement of what you tested against. In a container, the base image is the pin: `FROM python:3.12.4` is a different contract from `FROM python:3.12`, which is different again from `FROM python`, which means whatever the registry has today.

What you will see

True. A patch bump satisfies the floor; a minor bump to 3.13 would not if your floor were 3.12.x.

About 1 min of reading

Try It

Make a build reproducible by pinning

1

Find the unpinned fetch

grep -n '^FROM' Dockerfile

Before you run it, say out loud what it will print. Then open the answer.

What it prints
A FROM line. If it has no version, it is a moving pointer.
2

Pin the base image

sed -i 's/^FROM python:3.12$/FROM python:3.12.4/' Dockerfile

Before you run it, say out loud what it will print. Then open the answer.

What it prints
The FROM line now names an exact patch.
3

Record what resolved

docker build -t app:v1.4.2 . && docker images --format '{{.ImageName}} {{.Tag}}' | grep python

Before you run it, say out loud what it will print. Then open the answer.

What it prints
The base image tag recorded in the local store matches the pin.
4

Prove the floor works

python -c "from packaging.version import Version as V; print(V('3.12.9') >= V('3.12.4'))"

Before you run it, say out loud what it will print. Then open the answer.

What it prints
True. A patch bump satisfies the floor; a minor bump to 3.13 would not if your floor were 3.12.x.
Pinning an exact patch means you must bump the pin when the patch is yanked. That is the cost of reproducibility; accept it deliberately.

Data Flow

Pin to resolved artifact
1 Declare the pin in the recipe or lock file
↓
2 Resolve against the registry at build time
↓
3 Record the resolved version in the lock
↓
4 Compare the lock on every rebuild
Why this works
Why a floor is not a point

A point pin means every patch release forces you to rebuild and re-test. A floor lets patches in and still excludes the major that breaks. Choose the point only for artifacts you ship as-is.

Where pins fail

Pins fail when something resolves a version at runtime: a package index, a model download, a base image pulled with no tag. Grep your build for unpinned fetches before you trust the pin.

Examples

Three levels of pin

python means latest, python:3.12 means latest patch of that minor, python:3.12.4 means exactly that build. Only the last one is reproducible across machines.

Express the floor, not the point

For libraries, say '>=1.4.0,<2.0.0' so a patch release does not force a rebuild. For a base image whose output you ship, pin the point.

Lock files are pins

A lock file records the resolved versions. Commit it. The lock is the record of what you tested; without it, a rebuild on another machine can resolve different versions and pass while yours failed.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Pre-releases and release candidates
Previous Lesson
Pre-releases and release candidates
Next Lesson
Checking a container before you ship it
Checking a container before you ship it →