Skip to content
ohsudo, Home
Linux

pip "error: externally-managed-environment": install Python packages the right way

Fix pip "externally-managed-environment" on Ubuntu 24.04 and Debian 12: use apt, a virtual environment or pipx, and know when --break-system-packages is acceptable.

Beginner3 min readApplies to Ubuntu 23.04+ · Debian 12+ · Python 3.11+ · Docker

by OhSudo ·

You install a Python package the way every tutorial says, and pip refuses:

$ pip install requests
error: externally-managed-environment
× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
python3-xyz, where xyz is the package you are trying to
install.
If you wish to install a non-Debian-packaged Python package,
create a virtual environment using python3 -m venv path/to/venv.
Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
sure you have python3-full installed.
If you wish to install a non-Debian packaged Python application,
it may be easiest to use pipx install xyz, which will manage a
virtual environment for you. Make sure you have pipx installed.
See /usr/share/doc/python3.12/README.venv for more information.
note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.

This isn’t a bug. Since Debian 12 and Ubuntu 23.04, the system’s Python belongs to the operating system: tools like apt, cloud-init, ufw and many more are written in it, and they rely on exact package versions. pip installing into the same place could replace those packages and break them. So the distribution marks this Python as “externally managed” (PEP 668), and pip stops. The message itself lists the three right ways. Here’s which one to use when.

TL;DR

You want to… Use
Use a common library in system scripts sudo apt install python3-requests
Work on a project / install anything from PyPI A virtual environment: python3 -m venv .venv
Install a command-line tool (httpie, ansible, black…) pipx install httpie

1. The package from apt

Many popular libraries are packaged by the distribution as python3-<name>:

Terminal window
apt search python3-requests
sudo apt install python3-requests python3-yaml

They are tested with the system’s Python and updated with apt upgrade. The catch: you get the version the distribution chose, which can be older than the newest one on PyPI. Fine for system scripts; for a real project, use a virtual environment.

2. A virtual environment (for projects)

A virtual environment is a folder with its own Python and its own packages. pip may install anything there, and nothing outside it is affected:

Terminal window
sudo apt install python3-venv # once (python3-full also brings it)
python3 -m venv .venv # create it in your project folder
source .venv/bin/activate # use it in this shell
pip install requests # works — installs into .venv

While it’s active, python and pip are the ones in .venv. deactivate leaves it. In scripts, cron jobs and systemd services you don’t need activate — call the environment’s Python directly:

Terminal window
/opt/myapp/.venv/bin/python /opt/myapp/main.py

Keep the list of packages in requirements.txt (pip freeze > requirements.txt) and put .venv/ in .gitignore: the folder is easy to recreate, and it doesn’t work if you copy it to another path.

3. pipx (for command-line tools)

For tools you run as commands, a project folder is overkill. pipx creates one hidden virtual environment per tool and puts the command on your PATH:

Terminal window
sudo apt install pipx
pipx ensurepath # adds ~/.local/bin to PATH (open a new shell after it)
pipx install httpie
http --version

pipx upgrade-all updates every tool, pipx uninstall httpie removes one cleanly.

4. –break-system-packages: when it’s acceptable

The flag does exactly what it says: it lets pip write into the system’s Python anyway. On a server or your workstation, don’t — the day pip upgrades a package that apt or cloud-init depends on, those tools start failing in confusing ways, often at the next OS upgrade.

pip install --user doesn’t avoid the problem either: it’s blocked too, because packages in ~/.local still override the system’s ones for your user.

The one place it’s reasonable is a container that exists only to run your app, built from a Debian or Ubuntu image with the system’s Python:

FROM debian:12-slim
RUN apt-get update && apt-get install -y --no-install-recommends python3 python3-pip \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --break-system-packages --no-cache-dir -r requirements.txt

Even there, a virtual environment in the image is cleaner. Or simply start from the official python image — its Python isn’t the distribution’s, so pip works there without any flag.

Warning

Don’t delete the EXTERNALLY-MANAGED file in /usr/lib/python3.* to make the message go away. It’s the same as --break-system-packages for everything, forever — and the next Python package update from apt puts it back anyway.

Shell, we have a problem!

Similar, but different: bash: pip: command not found — pip isn’t installed: sudo apt install python3-pip, or use a virtual environment, which always has its own pip. sudo pip install … failing the same way isn’t a permissions problem: with or without sudo, the system’s Python is protected. If the install worked and Python still says No module named, you installed it for another Python.

Found this useful? Share it.

Found a mistake? Let us know at contact@ohsudo.com.

Articles

Related articles