pip on Python: Installing, Pinning and Cleaning Up Packages
This article covers pip as it behaves on a current Linux server: installing packages from PyPI, isolating them per project, pinning versions, and cleaning up when something breaks. It assumes you have shell access and Python 3 already on the box.
Requirements
- Python 3.8 or newer. Python 2.7 is end-of-life since January 2020 and pip dropped support for it in version 21.0.
- pip 23.1 or newer. The dependency resolver introduced in pip 20.3 is the one you want; older versions will happily install conflicting packages without warning.
- Write access to the target Python environment, or `sudo` if you are installing system-wide.
- Network access to `pypi.org` and `files.pythonhosted.org` over HTTPS.
```bash python3 --version pip3 --version ```
On Debian and Ubuntu, `pip` and `pip3` are separate binaries. `pip` historically pointed at Python 2, `pip3` at Python 3. Since Python 2 is gone from most current distros, `pip3` is the safe spelling. If both resolve to the same Python 3 interpreter on your system, that is fine — but do not assume it.
Why the system Python is the wrong place to install
Every package you `pip install` into the system interpreter lands in `/usr/lib/python3/dist-packages` or `/usr/local/lib/python3.x/site-packages`, shared by every script and service on the machine.Two projects that need different versions of `requests` cannot coexist there. One of them breaks. On a server running multiple Python services, this is not a hypothetical — it is a Tuesday.
The fix is a virtual environment per project. It costs you an extra directory and one activation command. It buys you isolation, reproducibility, and the ability to delete a project by deleting a folder.
Steps
1. Verify pip is present and current
```bash python3 -m pip --version ```Using `python3 -m pip` instead of bare `pip` guarantees you are talking to the pip that belongs to that Python interpreter. When multiple Pythons are installed, bare `pip` can point at a different one than `python3`, and you will spend twenty minutes wondering why your imports fail.
If pip is missing or too old:
```bash python3 -m ensurepip --upgrade python3 -m pip install --upgrade pip ```
`ensurepip` ships with CPython since 3.4, so it works offline. The upgrade pulls from PyPI and needs network access.
2. Create a virtual environment
```bash python3 -m venv /srv/myproject/venv source /srv/myproject/venv/bin/activate ```The `venv` module is part of the standard library. You do not need `virtualenv` unless you are on Python 3.3 or older, or you need features like `--system-site-packages` handling that `venv` does not expose cleanly.
After activation, `which python` should point inside the venv. If it does not, the activation failed and every package you install from here goes to the system interpreter.
3. Upgrade pip inside the venv
```bash python -m pip install --upgrade pip ```The pip that `venv` creates is whatever version was bundled with your Python build. On a Python 3.8 install from 2020, that is pip 20.x, which has the old resolver. Upgrading inside the venv is a separate step from step 1 because the venv starts with its own copy.
4. Install a package
```bash python -m pip install requests==2.31.0 ```Pinning the version is not optional in production. `pip install requests` grabs whatever is newest at that moment. Tomorrow it grabs something else. Your build is now non-reproducible, and the failure will surface at the worst possible time.
pip reads the package metadata — `pyproject.toml` for modern packages, `setup.py` for older ones — resolves the dependency tree, downloads wheels or source distributions, and installs everything into `site-packages` of the active venv.
5. Install from a requirements file
```bash python -m pip install -r requirements.txt ```A `requirements.txt` is a list of pinned packages. Generate it from a working environment:
```bash python -m pip freeze > requirements.txt ```
`pip freeze` outputs every installed package with its exact version, including transitive dependencies. That is what you want for reproducible builds. It is also noisy: it captures packages you installed for debugging and forgot about. Prune the file by hand before committing it.
6. Install from Git or a local path
```bash python -m pip install git+https://github.com/psf/requests.git@v2.31.0 python -m pip install -e /srv/myproject/src ```The `git+https://` form clones the repository, builds the package, and installs it. Pin a tag or commit hash after the `@` — pointing at `main` means your build changes whenever upstream pushes.
The `-e` flag installs in editable mode: the package is linked, not copied, so edits to the source take effect without reinstalling. Useful during development, wrong for production.
7. Inspect what is installed
```bash python -m pip list python -m pip show requests python -m pip check ````pip list` gives you names and versions. `pip show requests` prints the version, dependencies, author, license, and install location for one package — the fastest way to confirm which copy of a library your code is actually importing.
`pip check` compares every installed package against its declared dependencies and reports version conflicts. Run it after any bulk install. It catches problems that `pip install` did not, because pip will happily leave an inconsistent environment alone if you did not ask it to change anything.
8. Uninstall and clean up
```bash python -m pip uninstall requests python -m pip uninstall -r requirements.txt -y ```pip removes the package files but does not touch its dependencies. If `requests` pulled in `urllib3` and nothing else needs it, `urllib3` stays. There is no reliable automatic cleanup — pip cannot know which packages other tools depend on outside its own metadata.
The honest answer is that the cleanest way to remove a package and its orphaned dependencies is to delete the venv and rebuild it from `requirements.txt`. It takes thirty seconds and leaves no residue.
Verification
After installing, confirm the package imports in the interpreter you think you are using:```bash python -c "import requests; print(requests.__version__)" ```
If that prints the version you pinned, the install landed in the right environment. If it raises `ModuleNotFoundError`, you installed into a different interpreter than the one running the command — check `which python` and `python -m pip --version` again.
For a full environment audit:
```bash python -m pip check python -m pip list --outdated ```
`pip check` should print nothing if the environment is consistent. `pip list --outdated` shows what has newer versions available; it does not change anything.
Common problems
`externally-managed-environment` error on Debian 12 or Ubuntu 23.04+PEP 668 marks the system Python as externally managed. pip refuses to install into it.
```bash python3 -m venv /srv/myproject/venv source /srv/myproject/venv/bin/activate ```
Do not reach for `--break-system-packages`. It disables the guard that exists precisely to stop you from breaking `apt` and system tooling. The venv is the intended path.
Dependency resolver loops or takes minutes
pip 20.3 and later use a backtracking resolver. On large dependency graphs with conflicting constraints, it can churn. If it exceeds a minute, the constraints are probably unsatisfiable.
```bash python -m pip install --upgrade pip python -m pip install "packageA==1.2.3" "packageB==2.0.1" -v ```
The `-v` flag prints which candidates pip is trying and rejecting. That output tells you which two packages disagree. Loosen the pin on one of them, or find a version pair that works.
Installing a package fails to build from source
pip falls back to building from source when no wheel exists for your platform. That needs a compiler and headers.
```bash sudo apt install build-essential python3-dev ```
On RHEL-family systems: `sudo dnf groupinstall "Development Tools"` and `sudo dnf install python3-devel`. If the build still fails, check whether a newer version of the package ships a wheel — the maintainers may have fixed the build for your architecture.
PyPI is an open index. Anyone can upload a package, including one named `reqeusts` that looks close enough to `requests` to catch a typo. pip does not protect you from that. Pin your versions, read the package names before you hit enter, and keep your `requirements.txt` under version control — the file is the only record of what you actually meant to install.