Running pip install on a modern Linux distribution or a Homebrew Python now stops with:
error: externally-managed-environment
× This environment is externally managed
╰─> To install Python packages system-wide, try brew install
xyz, where xyz is the package you are trying to
install.
If you wish to install a Python library that isn't in Homebrew,
use a virtual environment:
python3 -m venv path/to/venv
source path/to/venv/bin/activate
python3 -m pip install xyz
...
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 is not a bug and not a broken pip. It is PEP 668 working as designed. Your Python belongs to something else — Debian's apt, Fedora's dnf, or Homebrew — and that packager has marked the installation off-limits to pip, because pip and the system package manager both write to the same site-packages and neither knows about the other's files.
The fix, in three lines:
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install <package>
Inside a virtual environment the error is gone, permanently, because the venv is yours.
Why This Happens
Before PEP 668, sudo pip install would happily overwrite a library that apt had installed and still considered its own. The next system upgrade would then either revert your change or break, and system tools written in Python — including, on some distributions, the package manager — could stop working. The failure came much later than the cause, which made it miserable to debug.
PEP 668 moves that failure to the moment it can still be avoided. A packaged Python ships a marker file, and pip refuses to write into the environment when it finds one:
python3 -c "import sysconfig, os; d = sysconfig.get_path('stdlib'); print(os.path.join(d, 'EXTERNALLY-MANAGED'), os.path.exists(os.path.join(d, 'EXTERNALLY-MANAGED')))"
/opt/homebrew/opt/python@3.13/Frameworks/Python.framework/Versions/3.13/lib/python3.13/EXTERNALLY-MANAGED True
That file also contains the message body you saw, which is why the suggested commands differ between machines — Homebrew tells you to brew install, Debian tells you to apt install python3-xyz. The error itself is identical; the advice is supplied by the packager.
Fix 1: A Virtual Environment (Use This)
For anything your own code imports, this is the correct answer:
cd /path/to/project
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
python3 -m pip install requests
Your prompt gains a (.venv) prefix while it is active. Confirm packages land in the right place:
python3 -c "import requests; print(requests.__file__)"
# /path/to/project/.venv/lib/python3.13/site-packages/requests/__init__.py
Add .venv/ to .gitignore and record dependencies instead of committing the directory:
python3 -m pip freeze > requirements.txt
You do not have to activate it to use it — calling the interpreter by path works and is more reliable in scripts, cron jobs and CI:
/path/to/project/.venv/bin/python -m pip install -r requirements.txt
/path/to/project/.venv/bin/python main.py
Fix 2: pipx, for Command-Line Tools
If the thing you are installing is an application rather than a library — black, ruff, httpie, awscli, poetry — a project venv is the wrong shape. pipx gives each application its own hidden venv and links just the executable onto your PATH:
brew install pipx # macOS
sudo apt install pipx # Debian / Ubuntu
pipx ensurepath
pipx install black
pipx install ruff
The tools then never conflict with each other or with your projects, and upgrading one cannot break another:
pipx list
pipx upgrade-all
Fix 3: Install the Distribution's Package
If your distribution already packages what you need, letting it manage the package is the most robust option — it will be upgraded with the rest of the system:
sudo apt install python3-requests # Debian / Ubuntu
sudo dnf install python3-requests # Fedora / RHEL
brew install python-requests # Homebrew, where available
The trade-off is version lag: distribution packages are often older than PyPI, and you cannot pin a specific version per project. For application dependencies, prefer a venv.
Fix 4: Containers
In a Dockerfile, build a venv and put it first on PATH. Everything afterwards — including plain pip install — then targets the venv automatically:
FROM python:3.13-slim
RUN python3 -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
This is worth doing even though a container is disposable. It keeps your dependencies separate from the base image's system Python, so an image rebuild on a newer base cannot silently change a library your app depends on.
The Last Resort: --break-system-packages
The flag exists, and the error message names it. It does exactly what it says:
python3 -m pip install --break-system-packages <package> # not recommended
This disables a safety check that exists for a real reason. With it, pip will overwrite files owned by
apt,dnforbrew. The failure does not appear at install time — it appears later, when a system upgrade collides with your version, or when an OS utility written in Python imports the library you replaced and gets an incompatible API. On Debian and Ubuntu, system tooling depends on specific versions of packages likerequestsandurllib3, so this can breakaptitself and leave you repairing the system by hand.
Making it permanent is worse, because it applies to every future install and to anyone who uses that account:
# ~/.config/pip/pip.conf — don't
[global]
break-system-packages = true
There is one defensible use: a throwaway container or VM that you rebuild from a Dockerfile and never upgrade in place. Even there a venv costs two extra lines and removes the risk entirely.
Verify the Fix
Confirm you are in a virtual environment and that the install landed there:
python3 -c "import sys; print(sys.prefix != sys.base_prefix)" # True inside a venv
python3 -m pip install requests
python3 -c "import requests; print(requests.__version__)"
For a pipx-installed tool, check what pipx is managing and that the executable resolves to its shim:
pipx list
which black
# ~/.local/bin/black
Prevention
- Create a venv as the first step of every new project, before the first
pip install. It costs one command and prevents this error entirely. - Use pipx for anything you run as a command, and venvs for anything you import.
- Never
sudo pip install. It was already the wrong thing before PEP 668; the error is now simply telling you so up front. - Put the venv creation in your project README and Makefile, so a new contributor's first
pip installis already inside one. - In CI and Docker, reference the venv interpreter by absolute path rather than relying on activation, which does not persist across shell invocations or Docker layers.