You run pip install (or pip3 install, or python3 -m pip install) and pip refuses. This is the output from pip 26.2.1 on Homebrew’s Python 3.14.7 on macOS, with the middle of the box shortened:
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.
[... venv, pipx and --break-system-packages advice omitted ...]
Read more about this behavior here: <https://peps.python.org/pep-0668/>
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.On Debian 12, Ubuntu 23.04 and newer, the first lines are the same and the box text comes from the distribution instead, suggesting apt install python3-xyz and a virtual environment; the exact wording varies by release.
It means this Python installation is managed by another package manager (Homebrew or apt), and pip will not write over files that package manager owns. Nothing is broken; the fix is to install somewhere you own.
Quick fix checklist
- Installing a library for a project: create a virtual environment and install there:
python3 -m venv .venv, then.venv/bin/python -m pip install <package>. - Installing a command-line tool (
black,httpie,yt-dlp,poetry):pipx install <tool>. Get pipx withbrew install pipxorsudo apt install pipx. - Want the library available to the OS Python itself: install the packaged version,
sudo apt install python3-<name>on Debian/Ubuntu. - On Debian/Ubuntu, if
python3 -m venvfails with anensurepip is not availablemessage, installpython3-venv(orpython3-full) with apt first. - Do not reach for
--break-system-packages, and never combine it withsudo.
Before you start
You need a terminal and to know which Python you are calling. pip has honoured this marker since version 23.0 (early 2023), and Homebrew’s Python, Debian 12 and Ubuntu 23.04 ship it, which is why tutorials written before then show pip install “just working”. If venvs are new to you, Python virtual environments covers them in depth.
Why it happens
PEP 668 gives a distributor a way to say “this interpreter’s packages are managed by me”: a file named EXTERNALLY-MANAGED in the interpreter’s standard library directory. You can look for it yourself:
$ python3 -c "import sysconfig, pathlib; p = pathlib.Path(sysconfig.get_path('stdlib'), 'EXTERNALLY-MANAGED'); print(p, p.exists())"
/opt/homebrew/opt/python@3.14/Frameworks/Python.framework/Versions/3.14/lib/python3.14/EXTERNALLY-MANAGED TrueThe file is a small INI document whose Error= value is the boxed message you saw. When pip is about to install into an interpreter that has the marker and is not running inside a virtual environment, it stops.
The reason is ownership. apt or Homebrew installed some Python packages into that interpreter’s site-packages and tracks them in its own database. Some of those packages are dependencies of system tools written in Python. If pip upgrades one (say urllib3 or cryptography) to a version a system tool was not built against, the tool can fail, and the package manager does not know the files changed. The next apt upgrade or brew upgrade may overwrite pip’s files, or refuse because they conflict. pip install --user is blocked too: packages in your user site directory are visible to the same interpreter, so they can still break tools you run.
A virtual environment has its own site-packages, owned by you, so pip installs there without complaint. That exemption is the whole design: system Python for the system, venvs for your code.
Step-by-step walkthrough
Step 1: Confirm which interpreter pip is targeting
$ python3 -m pip --version
pip 26.2.1 from /opt/homebrew/lib/python3.14/site-packages/pip (python 3.14)The path tells you whose Python it is: /opt/homebrew is Homebrew, /usr/lib/python3 is the OS. If you expected to be in a venv, it is not active. python3 -c "import sys; print(sys.prefix != sys.base_prefix)" prints False outside a venv.
Step 2: Decide what kind of thing you are installing
| You want | Use |
|---|---|
| A library your project imports | A virtual environment per project |
| A command-line application | pipx install <app> |
| A library the OS Python itself needs | apt install python3-<name> or brew install <name> |
| A quick throwaway experiment | A temporary venv you delete afterwards |
Step 3: Libraries go in a project virtual environment
$ python3 -m venv .venv
$ .venv/bin/python -m pip install -r requirements.txt
$ .venv/bin/python -c "import sys; print(sys.prefix != sys.base_prefix)"
TrueYou can source .venv/bin/activate to make python and pip refer to the venv for that shell session, or call .venv/bin/python directly, which is clearer in scripts and cron jobs because it needs no shell state. Point your editor at .venv/bin/python too. If you later see ModuleNotFoundError, the code is being run by a different interpreter than the one you installed into; see ModuleNotFoundError: No module named ‘x’.
Step 4: Command-line tools go in pipx
pipx creates one hidden virtual environment per application and links only its commands onto your PATH, so tools never share or fight over dependencies:
$ pipx install pycowsay
creating virtual environment...
installing pycowsay...
done! ✨ 🌟 ✨
installed package pycowsay 0.0.0.2, installed using Python 3.14.7
These apps are now available
- pycowsay
These manual pages are now available
- man6/pycowsay.6
$ pipx list
venvs are in $PIPX_HOME/venvs
apps are exposed on your $PATH at $PIPX_BIN_DIR
...
package pycowsay 0.0.0.2, installed using Python 3.14.7
- pycowsayThat is pipx 1.17.11 with PIPX_HOME and PIPX_BIN_DIR pointed at a scratch directory (shown as variables above); by default they live under your home directory, and the exact defaults differ by platform. If the bin directory is not on your PATH, pipx prints a warning during install; run pipx ensurepath once to fix it. Use pipx upgrade <app> and pipx uninstall <app> to manage them.
Step 5: Containers and CI
In a Dockerfile based on a distro image (debian, ubuntu) that installs python3 with apt, the marker is present and the same error appears during docker build. Create a venv in the image and put it first on PATH:
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends python3 python3-venv \
&& rm -rf /var/lib/apt/lists/*
RUN python3 -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txtThe official python:3.x images build Python from source into /usr/local without the marker, so pip install works there directly.
Worked scenario
A developer on a Mac with Homebrew Python keeps a small script that checks a status page and runs from cron:
import requests
resp = requests.get("https://www.python.org", timeout=10)
print(resp.status_code)Following an older README, they run:
$ python3 -m pip install -r requirements.txt
error: externally-managed-environment
× This environment is externally managedDiagnosis. python3 is Homebrew’s interpreter, which carries the marker. The README predates pip 23. The script is a project with its own dependency (requests==2.34.2 in requirements.txt), so it belongs in a venv. Even with the flag, packages installed into Homebrew’s interpreter would be left behind the next time Homebrew moves python3 to a new minor version, because each version has its own site-packages.
Fix. Create a venv next to the script and point cron at the venv’s interpreter:
$ cd ~/status-report
$ python3 -m venv .venv
$ .venv/bin/python -m pip install -r requirements.txt
$ .venv/bin/python report.py
200*/15 * * * * cd ~/status-report && .venv/bin/python report.py >> report.log 2>&1No activation step is needed in cron, because calling .venv/bin/python selects the environment. Add .venv/ to .gitignore and update the README.
Common mistake
The tempting fix is the one printed in the error itself:
sudo pip install --break-system-packages requests # don'tThe flag tells pip to ignore the marker and write into the package manager’s territory. Concretely, it can:
- upgrade or downgrade a library that an OS tool depends on, breaking that tool in a way that shows up days later;
- leave files apt or Homebrew does not know about, so the next upgrade overwrites them or fails on the conflict;
- with
sudo, write root-owned files into system directories, which is hard to undo cleanly.
Deleting the EXTERNALLY-MANAGED file or setting break-system-packages = true in pip.conf is the same decision made permanent, and the file returns with the next Python upgrade anyway. A venv costs one command and has none of these risks.
A related trap: a venv stops working if the Homebrew Python version it was created from is removed (for example after python3 moves to a new minor version and the old one is cleaned up). That is not this error; delete .venv and recreate it from requirements.txt.
Verify the behavior
Check three things: the code runs from the venv, the venv is really a venv, and the system interpreter was left untouched:
$ .venv/bin/python -c "import sys, requests; print(sys.prefix != sys.base_prefix, requests.__version__)"
True 2.34.2
$ python3 -m pip list
Package Version
------- -------
pip 26.2.1
wheel 0.47.0The Homebrew interpreter still has only its own packages. For a pipx tool, which <tool> should point into pipx’s bin directory, and pipx list should show it.
Interview exercise
“What problem does PEP 668 solve, how does pip detect an externally managed environment, and why are virtual environments exempt?”
Answer and reasoning
Before PEP 668, pip install (often with sudo) and the OS package manager both wrote into the same system site-packages. Neither knew about the other’s files, so pip could upgrade a dependency of an OS tool and break it, and later OS upgrades could silently overwrite or conflict with pip’s changes. PEP 668 lets the distributor declare ownership by placing an EXTERNALLY-MANAGED file in the interpreter’s standard library directory. pip 23.0 and newer check for it and refuse to install when the running interpreter has the marker and is not a virtual environment (detected because sys.prefix differs from sys.base_prefix inside a venv).
Venvs are exempt because they have their own site-packages that the package manager never touches, so there is no ownership conflict. The resulting rule is clean: the system interpreter belongs to the system, projects use venvs, and standalone tools use pipx. --break-system-packages reintroduces exactly the conflict the PEP was written to prevent.
Continue learning
More on environments and packaging in the Python interview questions and the Python MCQs. Related notes: Python virtual environments, ModuleNotFoundError: No module named ‘x’ and import side effects. Official references: PEP 668, the PyPA specification for externally managed environments, installing packages using pip and virtual environments, the venv module and the pipx documentation.