</>CodeWithKarani

pip 'externally-managed-environment': What PEP 668 Protects and the Right Fix

Karani GeoffreyKarani Geoffrey6 min read

You are on a fresh Debian 12 box, or Ubuntu 24.04, or inside a python:3.12-bookworm container, you type the most ordinary command in the world, and Python slaps your hand:

error: externally-managed-environment

× This environment is externally managed
…

Your first instinct, fed by the top three search results, is to reach for --break-system-packages. Do not. That flag is named as a warning, not a suggestion. It does exactly what it says on modern distros, it lets pip overwrite packages your operating system depends on, and the way you find out is when apt or a system tool stops working weeks later with an error that gives no hint it was your pip install that did it.

This is not a pip bug and it is not a regression to work around. It is PEP 668 doing precisely what it was designed to do: stop pip from fighting apt and dnf over the same files. The right fix takes one extra line and never bites you again.

Your system Python is managed by apt/dnf, and PEP 668 blocks pip from installing into it to prevent the two package managers from corrupting each other. The correct fix is a virtual environment:

python3 -m venv .venv
source .venv/bin/activate
pip install whatever-you-wanted

Inside the venv, pip works normally with no flags. Reserve --break-system-packages for throwaway containers only, never a machine you care about. And pip install --user is not a reliable workaround, on some pip/OS combinations it triggers the exact same error.

The full error, verbatim

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.
    …
    If you wish to install a non-Debian packaged Python application,
    it may be easiest to use pipx 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.

Read the last paragraph again: "at the risk of breaking your Python installation or OS". That is not boilerplate. It is the accurate description of what the flag does.

Why this happens: two package managers, one directory

On Debian, Ubuntu, Fedora and their relatives, the system Python is not just a language runtime, it is a dependency of the operating system. apt installs Python packages (as python3-requests, python3-yaml and so on) because system tools are written in Python and import them. Those files live in the system site-packages.

pip, historically, installs into that same directory. So you had two package managers, apt and pip, both writing to the same site-packages, neither aware of the other. When pip upgraded a package that a system tool depended on to a version the tool did not expect, the tool broke, silently and confusingly. This caused years of "I ran pip install and now apt is broken" bug reports that were nearly impossible to diagnose.

PEP 668 fixes this at the root. A distro that manages its Python drops a marker file next to the interpreter:

ls /usr/lib/python3.*/EXTERNALLY-MANAGED

When that file exists, pip refuses to install into the system environment and prints the error. It is a contract: this Python belongs to the OS, use your own environment for your own packages. Since Python 3.11 and pip 23.0 the marker is honoured, and recent Debian and Ubuntu ship it.

System Python (/usr/lib/python3.x) EXTERNALLY-MANAGED marker present apt / dnf pip BLOCKED Two managers, one dir = corruption. PEP 668 stops pip here. Project venv (./.venv) yours alone, no marker pip install (works) One manager, isolated dir. Nothing the OS relies on.
The fix is not to force pip past the wall. It is to stop installing behind the wall in the first place.

The correct fix, in numbered steps

Step 1: Create a virtual environment

A venv is a private copy of Python's package directory that belongs to your project, not the OS. pip installs there freely because nothing the operating system depends on lives inside it.

cd /path/to/your/project
python3 -m venv .venv

If this itself errors with ensurepip is not available, install the venv package first: sudo apt install python3-venv (or the version-specific python3.12-venv). That is the one thing you legitimately install system-wide, because it is the tool for making venvs.

Step 2: Activate it and install normally

source .venv/bin/activate
# prompt now shows (.venv)
pip install requests fastapi   # no flags, no error

Inside the activated venv, which python and which pip point into .venv/bin, and PEP 668 does not apply because this environment has no EXTERNALLY-MANAGED marker. This is the same isolation that keeps a FastAPI project's dependencies from colliding with the system.

Step 3: For command-line tools, use pipx instead

If what you wanted was an application you run from the shell (black, httpie, poetry, ansible), not a library to import, pipx is the right tool. It installs each app into its own isolated venv and puts just the command on your PATH:

sudo apt install pipx     # or: python3 -m pip install --user pipx (in a venv-free base)
pipx ensurepath
pipx install black

This is exactly the case the error message itself points you to, and it is the cleanest answer for CLI tools you want available everywhere without polluting any Python.

Verification: prove you installed in the right place

After activating and installing, confirm the package landed in the venv and not the system Python:

which python          # -> /path/to/project/.venv/bin/python
pip show requests | grep Location
# Location: /path/to/project/.venv/lib/python3.x/site-packages

If Location points anywhere under /usr/lib or /usr/local/lib, you installed into the system Python and the venv is not active. Re-run source .venv/bin/activate. The whole point is that the Location is inside your project.

What people get wrong

  • Reaching for --break-system-packages first. On a real machine this is how you eventually break apt. The flag disables the protection that exists specifically because pip and apt corrupt each other. Use a venv.
  • "Just use pip install --user." This is the most repeated "safe" workaround and it is not reliable. --user installs into your home directory, not system site-packages, so in theory PEP 668 should not apply, but on several pip and OS combinations it triggers the exact same externally-managed-environment error. This inconsistency is real and documented in pip's own tracker, so do not build a workflow on --user. Use a venv, which always works.
  • Deleting the EXTERNALLY-MANAGED file. Yes, removing that file makes the error go away. It also re-enables the exact footgun the file exists to prevent. You are not fixing anything, you are disabling the seatbelt.
  • Running sudo pip install. That is the worst version of all: it forces packages into system Python as root, maximising the blast radius when it collides with an OS package.

The Docker angle: which base images hit this

This trips people migrating Dockerfiles. Whether you hit PEP 668 depends on the base image:

Base imageExternally managed?What to do
python:3.12-slim / python:3.12 (official)No marker by defaultPlain pip install works, though a venv is still cleaner
python:3.12-bookworm (Debian-based, some setups)May be managedCheck the marker; prefer a venv
debian:12 / ubuntu:24.04 + apt install python3YesCreate a venv in the image, or use --break-system-packages only because the container is disposable

Confirm for any image with one line:

test -f /usr/lib/python3*/EXTERNALLY-MANAGED && echo managed || echo not-managed

In a distro-based image where the container is genuinely throwaway and rebuilt from scratch every deploy, --break-system-packages is defensible, because there is no long-lived OS to corrupt. That is the one place the flag is reasonable. The cleaner pattern even there is a venv baked into the image:

RUN python3 -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install -r requirements.txt

When it is still broken

  1. python3 -m venv fails with ensurepip is not available. Install the venv module for your Python version: sudo apt install python3-venv or the versioned python3.12-venv.
  2. The venv activates but pip still points at system Python. Your shell cached the old path. Run hash -r, or open a new shell and re-activate.
  3. A tool you did not write runs pip install internally and fails. Point it at a venv by activating first, or set PIP_REQUIRE_VIRTUALENV off is not the fix, running the tool inside an activated venv is.
  4. You need one package importable by a system script. Package it the OS way (apt install python3-thepackage) if it exists, rather than forcing pip into system Python.

PEP 668 feels like an obstacle the first time and a favour every time after. The system Python belongs to the OS. Your code belongs in a venv. Make that split once and this error simply stops happening.

Frequently asked questions

What does 'error: externally-managed-environment' mean?
It means the system Python is managed by your OS package manager (apt or dnf), and PEP 668 blocks pip from installing into it to stop the two package managers from corrupting each other's files. It is not a pip bug or a regression. The distro placed an EXTERNALLY-MANAGED marker next to the interpreter, and pip honours it. Install into a virtual environment instead.
Is --break-system-packages safe to use?
Not on a machine you care about. It disables the exact protection PEP 668 adds, letting pip overwrite packages the operating system depends on, which can break apt or system tools later with errors that give no hint pip caused them. It is only defensible in a throwaway container that is rebuilt from scratch, where there is no long-lived OS to corrupt. On a real system, use a venv.
Why does pip install --user also give the externally-managed error?
Because on several pip and OS combinations the PEP 668 check triggers even for --user, even though --user installs into your home directory rather than system site-packages and in theory should not be affected. This inconsistency is real and documented in pip's issue tracker, so --user is not a reliable workaround. Create a virtual environment, which always works regardless of pip or OS version.
How do I fix this in a Docker image?
It depends on the base image. Official python:3.x-slim images usually have no EXTERNALLY-MANAGED marker so plain pip install works. Distro-based images like debian:12 or ubuntu:24.04 are managed. The clean pattern is to create a venv in the image (python3 -m venv /opt/venv) and put it on PATH, so pip install works normally. Check any image with: test -f /usr/lib/python3*/EXTERNALLY-MANAGED.
#pip#Python#PEP 668#virtualenv#Docker
Keep reading

Related articles