Only show guides for your app
== vs >= vs ~= in requirements.txt, what each one resolves to, and why the Python app's install-on-every-start makes pinning matter more here than on your laptop.
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 |
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.
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 within 21, but never 22.~=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.
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."
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.
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.)
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.~= instead of ==) so a common version exists, and restart.== and it stops moving.The full version-specifier syntax (including !=, .*, and pre-release rules) is documented at pip.pypa.io.