Your requirements.txt doesn't just list packages — the version rules next to each name decide exactly what gets installed. Get them right and your app installs the same versions every time; get them loose and a dependency can quietly jump to a new major release and break your app overnight. This matters more on Falix than most places, because the Python application re-runs the install on every start. This guide covers each version operator, what it actually resolves to, and how to pin for reproducible installs.
| At a glance | |
|---|---|
| You need | A Falix server running the Python application |
| Plan | Any |
| Time | Fifteen minutes |
Why pinning matters more here
On start, the Python app installs your requirements with an upgrade flag — roughly pip install -U --prefix .local -r requirements.txt. The -U is the important part: for any package whose version rule allows something newer, pip pulls the newest version that still satisfies the rule, every single start. So a loose rule doesn't just affect the first install — it can hand you a different version each time the server restarts.
That's convenient when you want the latest patch, and a trap when a new release changes behaviour under you. Pinning is how you decide which it is.
The operators, and what they resolve to
Say a package demo has published versions 21.0, 21.3, 22.0, and 26.2 (newest). Here's what each rule installs:
In requirements.txt |
Means | Installs |
|---|---|---|
demo==21.0 |
Exactly this version | 21.0 |
demo>=21.0 |
This or anything newer (no upper bound) | 26.2 (the newest) |
demo~=21.0 |
"Compatible release": >=21.0, <22.0 |
21.3 (newest 21.x) |
demo~=21.3 |
>=21.3, <22.0 |
21.3 |
demo>=21.0,<23 |
An explicit range | 22.0 (newest under 23) |
demo |
No rule at all | 26.2 (the newest, and re-checked every start) |
The one people misread is ~= (compatible release). It pins the left-most part and lets the rest float:
~=21.0→>=21.0, <22.0— patch and minor updates within21, but never22.~=21.3→>=21.3, <22.0— same upper bound, higher floor.~=21.3.1→>=21.3.1, <21.4.0— with three parts, only the patch floats.
🎯 Good to know:
>=has no ceiling.flask>=2.0will happily install Flask 3.x — a different major version — the next time a new one ships and your server restarts. If you don't want major bumps, use~=or an explicit upper bound instead.
How to pin, in practice
Match the rule to how much change you'll tolerate:
# Exact — the safest, most reproducible. Nothing moves until you change it.
discord.py==2.4.0
flask==3.0.3
# Compatible releases — get bug fixes, never a surprise major.
requests~=2.32
python-dotenv~=1.0
# Only where you genuinely want the latest and accept the risk:
some-tool>=1.0
For a project you care about keeping stable, pin exact versions (==). It's the difference between "it installed the same thing it did last week" and "it installed whatever was newest this morning."
Getting a known-good set
Install and test your app until it works, then capture the exact versions you have. Locally, pip freeze prints every installed package at its exact version — paste the lines you depend on into requirements.txt. On Falix, the Packages page shows each dependency's Requested / Installed / Latest versions side by side, so you can see at a glance where a loose rule has drifted ahead of what you pinned.
Where the packages live
Installed packages go into a .local folder in your server, which isn't yours to keep — a reinstall or an application switch wipes it. You never rebuild it by hand, though: the install runs again on the next start and restores everything from requirements.txt. That's the whole reason the file is the source of truth — guard it, not the installed folder. (Your own data belongs in a managed database, not in .local.)
Troubleshooting
ERROR: Could not find a version that satisfies the requirement ...— the version rule can't be met (a typo'd name, or a version that doesn't exist). Check the exact spelling and that the version is real. See Install failures.- Two packages demand conflicting versions — pip reports the clash during install. Loosen one pin (often
~=instead of==) so a common version exists, and restart. - "It worked last week, now it's broken" with no code change — a loose rule pulled a newer version on restart. Pin the working versions with
==and it stops moving. - Managing dependencies from pyproject.toml (poetry/uv)? — that's a different flow; see Poetry and uv on Falix.
The full version-specifier syntax (including !=, .*, and pre-release rules) is documented at pip.pypa.io.