Definition
You pick up a Python project you have never installed before, and nothing works on the first run: some libraries are missing, and nothing on screen says which ones. requirements.txt answers that exact annoyance by listing, one per line, what the project needs. It holds no code and Python never reads it itself: pip turned it into a de facto standard just by agreeing to open it, with no PEP ever defining it officially.
A blank line does not count, and neither does a line starting with a hash, which leaves room for a comment above each group. An example makes it obvious.
# Application dependencies
requests==2.32.3
flask==3.0.3
pandas==2.2.2A single command then rebuilds the entire environment, fetching every package from PyPI.
pip install -r requirements.txtThe name written in the file is the package's name on PyPI, not the name you later import: you run pip install Pillow but write import PIL.
The problem it solves
A Git repository keeps the code, never the libraries it uses. Whoever clones the project therefore gets files calling tools missing from their machine.
import requests
import pandas as pd
from flask import Flask
# Three libraries, none of them installed on a fresh machineWith no written list, you run the program, read the error, install the named library, run again, and start over for every orphan import line. With three libraries that is manageable; with the thirty a real application uses, it becomes a lost afternoon, one every new developer loses again on their first day. The file replaces that hunt with a single command: the server installs the same list the development machine had, and "it works on my machine" stops being an acceptable explanation.
Pinning versions, or not
The same library, written three different ways, will not produce the same installation six months later. Here is what each writing means for pip.
| Writing | What pip installs | When to choose it |
|---|---|---|
flask | The latest published version, whatever it is | Never on a deployed project |
flask>=3.0 | Any version from 3.0 upwards | For a library others will install |
flask==3.0.3 | That exact version, and no other | For an application going live |
An application lives alone on its server: it has nothing to negotiate and can pin the exact version that was tested. A library, on the other hand, will sit alongside others, chosen by someone else: demanding an exact version of each of its own dependencies would collide with the first other library in the project asking for a different one. So it loosens its constraints instead, and leaves the application to decide.
pip freeze, and the trap that comes with it
Writing a file of several dozen lines by hand would be tedious, so pip offers to do it for you.
pip freeze > requirements.txtThis command copies out everything installed in the current environment: with no virtual environment active, it also grabs tools from other projects, turning three real dependencies into a hundred and fifty lines. The fix is to create a venv per project before installing anything, not to clean the file by hand.
A second effect is quieter: pip freeze mixes chosen libraries with the ones they brought along as indirect dependencies. Nothing then tells one apart from the other, and removing a library later turns into archaeology.
Installing a library with pip install does not update requirements.txt on its own: the project keeps working locally until the first deployment, where the import fails because the file was never completed before committing it.
requirements.txt or pyproject.toml
The latter is the official format for describing a Python project, and it is tempting to present it as the natural successor to the former. The truth deserves to be more nuanced.
| Criterion | requirements.txt | pyproject.toml |
|---|---|---|
| Role | List what must be installed | Describe the project and its dependencies |
| Indirect dependencies | Mixed in with the rest | Kept apart, frozen by a lock file |
| Tooling | pip is enough | A dedicated manager, such as poetry |
| Publishing a library | Out of reach | That is its primary use |
The newer format does not make the older one obsolete, it answers a different need: plenty of hosting providers and Docker images still look for a requirements.txt and nothing else. A personal script is well served by the plain text file; a team wanting bit-for-bit identical installs needs the lock file.
What it does not do
Three reasonable expectations run into the limits of the format.
The first concerns the Python version itself: the file says nothing anywhere about which version the project needs, and a pinned requirements.txt will still install without complaint on an interpreter that is far too old, before the program breaks further along, on a syntax it does not recognise.
The second concerns the virtual environment. The file does not create it, it assumes one is already active, which explains most installations that ended up in the system Python by mistake.
The third concerns whatever is not Python at all: a dependency that quietly needs a compiler or a database client will fail on a bare machine. These limits are the price of its simplicity.
Frequently asked questions
Should requirements.txt be committed to the repository?
Yes, that is its whole reason to exist: it travels with the code and stays readable by whoever clones the project. What should never follow it is the virtual environment folder, heavy and valid only on the machine that created it.
Why does the installation fail when the file has not changed?
Because an indirect dependency was not pinned, and published an incompatible version in the meantime. The file itself did not change, but what it points at shifted under it: that is the central argument for a frozen list on anything going to production.
How can development tools be kept apart from real dependencies?
By writing a second file, requirements-dev.txt, whose first line pulls in the first one via the -r option, before adding pytest or ruff. The server then installs only the production list.