error: externally-managed-environment is the error every Python developer hits on a fresh install the first time they run pip install. It is a deliberate lock — PEP 668 makes pip refuse to install into the system Python because mixing pip-installed packages with the OS package manager breaks the system Python in ways that are hard to debug. The three real fixes are a virtual environment (python -m venv), pipx for CLI tools, or the --break-system-packages flag for cases where you genuinely need to modify the system Python. Each has a use case. None are decorative.
Table of contents
- Table of contents
- The direct answer
- Why this error exists (the PEP 668 backstory)
- Fix 1: a virtual environment for every project
- Fix 2: pipx for CLI tools
- Fix 3: —break-system-packages for disposable Python
- The five situations where one fix is better than the others
- The migration recipe from a broken system Python
- FAQ
- FAQ
The short version: create a virtual environment for every project. Use pipx install <tool> for CLI tools you want available globally. Use --break-system-packages only inside a Docker container where the OS Python is already throwaway. Never use --break-system-packages on your development machine — that is how you end up reinstalling your OS.
Table of contents
- The direct answer
- Why this error exists (the PEP 668 backstory)
- Fix 1: a virtual environment for every project
- Fix 2: pipx for CLI tools
- Fix 3: —break-system-packages for disposable Python
- The five situations where one fix is better than the others
- The migration recipe from a broken system Python
- FAQ
The direct answer
The error in full:
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 application,
it may be easiest to use pipx install xyz, which will manage a
virtual environment for you. You can install pipx via apt (sudo apt
install pipx) or via pip (pip install --user pipx).
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 pipx is installed.
See /usr/share/doc/python3.11/README.venv for more information.
The three fixes:
# 1. Virtual environment (for projects)
python -m venv .venv
source .venv/bin/activate
pip install <package>
# 2. pipx (for CLI tools)
pipx install <cli-tool>
# 3. Force install into the system Python (Docker only)
pip install --break-system-packages <package>
Pick the right one for the situation. The rest of this post covers which is which.
Why this error exists (the PEP 668 backstory)
Before PEP 668 (Python 3.11), pip install would happily install packages into the system Python. The result was a system Python that was half-managed by apt/yum/dnf and half-managed by pip. When the OS upgraded a Python package, pip’s version was overwritten. When pip upgraded a package, the OS sometimes broke. The breakage was silent and confusing.
PEP 668 introduced a marker file (/usr/lib/python3.X/EXTERNALLY-MANAGED) that signals “this Python is managed by the OS, do not modify it with pip.” When pip sees the marker, it refuses to install system-wide unless given an explicit override.
The marker is present by default on Debian 12+, Ubuntu 23.04+, Fedora 36+, and most modern Linux distros. Python 3.11+ on these distros ships with the marker. Python 3.10 and earlier do not, even on the same distros.
The marker is a feature, not a bug. The “fix” is to stop fighting it and use the right tool for the job.
Fix 1: a virtual environment for every project
The right answer for application code. A virtual environment is a directory that holds a private copy of pip and a private site-packages directory. When you pip install inside the venv, packages go to the venv’s site-packages, not the system Python’s.
The setup:
# Create a venv
python -m venv .venv
# Activate it
source .venv/bin/activate
# Now pip installs to the venv
pip install requests fastapi
# The venv's Python is on PATH
which python
# /home/me/project/.venv/bin/python
The activation is per-shell. When you close the shell, the activation goes away. Re-activate with source .venv/bin/activate (or the equivalent for your shell — Fish uses . .venv/bin/activate.fish, Windows uses .venv\Scripts\activate).
To deactivate:
deactivate
The venv pattern works for every project, every language, every deployment target. The venv is portable: copy it to another machine, the dependencies travel with it.
For projects that need a specific Python version, use a tool like pyenv to install the version, then python -m venv with that version’s Python:
pyenv install 3.11.5
pyenv local 3.11.5
python -m venv .venv
source .venv/bin/activate
The venv is created with the currently-active Python.
Fix 2: pipx for CLI tools
The right answer for command-line tools. pipx installs Python CLI tools into isolated venvs and exposes the tool’s executables on your PATH. The tool’s dependencies are isolated from your other Python tools.
pipx install black
pipx install ruff
pipx install poetry
Each tool gets its own venv at ~/.local/pipx/venvs/<tool>/. The tool’s binary is symlinked into ~/.local/bin/, which you add to PATH.
Why pipx and not pip install --user black?
pip install --userinstalls into~/.local/lib/python3.X/site-packages/. Multiple tools can collide on shared dependencies. Black and Ruff both depend onclick; one will win the version race.- pipx creates per-tool venvs. Black and Ruff each get their own
click. No conflicts.
pipx run is also useful for one-off execution:
pipx run pyflakes src/
# Runs pyflakes without installing it
For tools you want available everywhere (linters, formatters, build tools), pipx is the right answer. For project dependencies (FastAPI, Django, requests), venvs are the right answer. For data-science notebooks, conda or uv has its own pattern.
Fix 3: —break-system-packages for disposable Python
The escape hatch. Use it when the Python is genuinely disposable — most commonly, inside a Docker container where the OS Python is going to be thrown away after the build.
FROM python:3.11-slim
RUN pip install --break-system-packages -r requirements.txt
The --break-system-packages flag tells pip “I know this is a managed Python, override the lock.” It is appropriate when the Python is built into an immutable image and the image is going to be replaced on the next build.
It is inappropriate on a development machine. The system Python is the OS’s, and modifying it can break apt, system tools, and your own environment. If you find yourself reaching for --break-system-packages on a laptop, the answer is to use a venv or pipx instead.
A common mistake: a Dockerfile that uses the python:3.11-slim base and tries to install a tool with pip install --break-system-packages without first creating a venv. The tool ends up in the system Python’s site-packages. The build works. The runtime is a black box.
The pattern that does not break:
FROM python:3.11-slim
RUN python -m venv /app/venv
ENV PATH="/app/venv/bin:$PATH"
RUN pip install -r requirements.txt
The venv lives in the image. The PATH is updated so the venv’s Python is used. No --break-system-packages needed.
The five situations where one fix is better than the others
1. Application code. Use a venv. Always. The pattern: python -m venv .venv in the project root, source .venv/bin/activate to activate, pip install -r requirements.txt to install.
2. CLI tools you want available globally. Use pipx. pipx install black puts black on your PATH with its own venv. No conflicts with other tools.
3. A Docker container. Use a venv inside the container, not --break-system-packages. The venv is portable and reproducible.
4. A one-off script you want to run once. Use pipx run <tool> or a temporary venv. Do not modify the system Python.
5. A Python tool that needs to be available to other system tools (rare). Use --break-system-packages and accept the consequences. Document what you did. This is the rare case; if you find yourself here, you probably want to use a venv and add the venv’s bin to PATH instead.
The migration recipe from a broken system Python
If you have already broken your system Python with pip install (or you inherited a machine that way), the recovery is:
# 1. Identify what was installed outside the system
pip list --user
# 2. Note the package list
pip list --user --format=freeze > /tmp/broken.txt
# 3. Remove the user-site directory (the most common breakage location)
rm -rf ~/.local/lib/python3.X/site-packages
# 4. Reinstall what you actually need in a venv or via pipx
python -m venv .venv
source .venv/bin/activate
pip install -r /tmp/broken.txt
# 5. For CLI tools, use pipx
pipx install black
pipx install ruff
After the cleanup, the system Python is back to its pristine state, your project deps are in a venv, and your CLI tools are in pipx. Future pip install attempts will fail with the PEP 668 error — which is the correct behavior.
For a Python app deployed to a managed platform like RunxBuild’s backend services, the build pipeline creates a venv inside the container and installs requirements.txt into it. No PEP 668 issue; the system Python is never modified. For the cost of running that Python service at production scale, the RunxBuild hosting calculator gives you the per-month number.
FAQ
What is PEP 668?
A Python Enhancement Proposal that introduces the EXTERNALLY-MANAGED marker file. PEP 668 tells pip “this Python is managed by the OS, do not modify it.” Python 3.11+ enforces it on Debian, Ubuntu, Fedora, and most modern distros.
Can I disable the PEP 668 check globally?
Yes, by removing the EXTERNALLY-MANAGED marker file. Do not do this. The marker exists for a reason. Use a venv or pipx instead.
What is the difference between pipx and a venv?
pipx manages per-tool venvs for CLI tools and exposes their binaries on PATH. A venv is a project-level sandbox you activate per-shell. Use pipx for tools; use venvs for projects.
Can I use pipx inside a Docker container?
Yes. pipx install <tool> works the same way inside a container as outside. Some teams use pipx to install build-time tools (linters, formatters) in Docker.
Is pip install --user still a thing?
Yes, but it is the wrong answer for almost everything. --user installs into ~/.local/lib/python3.X/site-packages/. Multiple tools can collide on shared dependencies. Use a venv or pipx.
What is uv?
A fast Python package manager written in Rust. uv pip install and uv venv are drop-in replacements for pip and venv, with much faster installation and resolution. uv handles the PEP 668 lock correctly out of the box.
Does Poetry handle PEP 668 correctly?
Yes. Poetry creates a venv by default and installs into it. You will not see the PEP 668 error with Poetry unless you explicitly use poetry config virtualenvs.create false (which puts Poetry into “install to system Python” mode — usually a mistake).
Why does my Dockerfile fail with this error?
Your base image is modern (Python 3.11+ on Debian 12+). The fix is to create a venv in the Dockerfile and install into it, or to use pip install --break-system-packages if the OS Python is genuinely disposable (it usually is inside a container).
FAQ
What is PEP 668?
A Python Enhancement Proposal that introduces the EXTERNALLY-MANAGED marker file. It tells pip “this Python is managed by the OS, do not modify it.”
Can I disable the PEP 668 check globally?
Yes, by removing the marker file. Do not do this. Use a venv or pipx instead.
What is the difference between pipx and a venv?
pipx manages per-tool venvs for CLI tools. A venv is a project-level sandbox you activate per-shell.
Can I use pipx inside a Docker container?
Yes. pipx install <tool> works the same way inside a container as outside.
Is pip install --user still a thing?
Yes, but it is the wrong answer for almost everything. --user installs into ~/.local/lib/python3.X/site-packages/. Use a venv or pipx.
What is uv?
A fast Python package manager written in Rust. uv pip install and uv venv are drop-in replacements for pip and venv, with much faster installation.
Does Poetry handle PEP 668 correctly?
Yes. Poetry creates a venv by default and installs into it. You will not see the PEP 668 error with Poetry unless you disable the venv explicitly.
Why does my Dockerfile fail with this error?
Your base image is modern (Python 3.11+ on Debian 12+). The fix is to create a venv in the Dockerfile, or use --break-system-packages if the OS Python is disposable.