How pipx works¶
Note
This page describes the pip backend model, where a shared pip environment backs every managed venv through a
.pth file. When the uv backend is active (the default whenever uv is available, via the pipx[uv] extra
or on PATH) pipx skips the shared environment and uses uv venv and uv pip instead; uv-created venvs ship
without pip. See Use the uv backend.
Installing an application¶
pipx install PACKAGE creates an isolated virtual environment under PIPX_HOME (for example
~/.local/share/pipx/venvs/black) and installs the package into it. Under the pip backend pipx first creates or
reuses a shared environment that holds an up-to-date pip and exposes it to each managed venv through a
.pth file, so every venv borrows one pip rather than installing its
own. Under the uv backend there is no shared environment: uv venv builds the venv (without pip) and uv pip
installs into it.
Once the package is installed, pipx exposes the application’s resources so you can reach them from anywhere. It handles four kinds:
console and GUI scripts, into
PIPX_BIN_DIR(default~/.local/bin), for example~/.local/bin/black->~/.local/share/pipx/venvs/black/bin/black;manual pages, into
PIPX_MAN_DIR(default~/.local/share/man/man[1-9]);shell completion scripts, into
PIPX_COMPLETION_DIR(default~/.local/share).
pipx symlinks each resource into place. On Windows, and on any filesystem that does not support symlinks, it copies the
file instead. As long as PIPX_BIN_DIR is on your PATH the commands are available globally, and on systems with
man support the pages are too.
pipx records what it installed and exposed in a per-venv pipx_metadata.json file. Later commands (upgrade,
reinstall, expose) read it to reproduce the same state.
Adding --global to any command targets a system-wide location shared by all users instead of your home directory;
the concrete paths and overrides live in Configure paths. It is not available on Windows.
For a .py script carrying PEP 723 metadata, pipx builds a temporary wheel from the script and its declared
dependencies, installs that wheel like any other package, then records the original path or URL so upgrade and
reinstall can rebuild it.
flowchart LR
A["pipx install black"] --> BK{"backend?"}
BK -- pip --> P["shared venv (pip)<br/>reused via .pth"]
BK -- uv --> U["uv venv<br/>(no pip)"]
P --> C["create venv<br/>venvs/black/"]
U --> C
C --> D["install package<br/>pip install / uv pip install"]
D --> E["expose apps<br/>~/.local/bin"]
D --> F["expose man pages<br/>~/.local/share/man"]
D --> G["expose completions<br/>PIPX_COMPLETION_DIR"]
classDef input fill:#3f72af,stroke:#28517f,color:#fff
classDef decision fill:#c78c20,stroke:#8a6011,color:#fff
classDef proc fill:#2a9d8f,stroke:#1c6b61,color:#fff
classDef out fill:#388e3c,stroke:#256128,color:#fff
class A input
class BK decision
class P,U,C,D proc
class E,F,G out
Running an application¶
pipx run APP executes an application without installing it permanently. pipx either reuses a cached temporary venv
or builds a fresh one, then invokes the app. The cache key is a hash of the package name, spec, Python version, and pip
arguments; cached environments expire after 14 days, after which the next run rebuilds against the latest release.
Under the pip backend the temporary venv borrows the shared pip; under the uv backend uv creates and populates it.
pipx run --with PKG adds extra dependencies to that temporary environment.
flowchart LR
A["pipx run pycowsay"] --> C{"cached<br/>venv?"}
C -- yes --> E["reuse cached venv"]
C -- no --> BK{"backend?"}
BK -- pip --> P["shared pip +<br/>python -m venv"]
BK -- uv --> U["uv venv + uv pip"]
P --> D["install pycowsay"]
U --> D
D --> F["invoke app"]
E --> F
classDef input fill:#3f72af,stroke:#28517f,color:#fff
classDef decision fill:#c78c20,stroke:#8a6011,color:#fff
classDef proc fill:#2a9d8f,stroke:#1c6b61,color:#fff
classDef out fill:#388e3c,stroke:#256128,color:#fff
class A input
class C,BK decision
class P,U,D,E proc
class F out
Exposing apps on PATH¶
When you type an exposed command, the shell finds the symlink (or copy) in PIPX_BIN_DIR and follows it to the
launcher inside the app’s venv. That launcher runs the venv’s own Python against the installed package, so the app
always uses its isolated dependencies and never your system site-packages.
flowchart LR
U["user types<br/>black"] --> B["~/.local/bin/black<br/>(symlink or copy)"]
B --> V["venvs/black/bin/black<br/>(launcher)"]
V --> P["venv Python<br/>+ installed black"]
classDef input fill:#3f72af,stroke:#28517f,color:#fff
classDef proc fill:#2a9d8f,stroke:#1c6b61,color:#fff
classDef out fill:#388e3c,stroke:#256128,color:#fff
class U input
class B,V proc
class P out
Directory layout¶
pipx keeps its state under one home directory. The tree below is the conceptual shape; the concrete locations, defaults, and every override live in Configure paths.
flowchart TD
HOME["~"] --> BIN["~/.local/bin/<br/>(on PATH)"]
HOME --> DATA["~/.local/share/pipx/"]
DATA --> SHARED["shared/<br/>(pip, pip backend only)"]
DATA --> VENVS["venvs/"]
VENVS --> V1["black/"]
VENVS --> V2["poetry/"]
VENVS --> V3["ruff/"]
V1 --> V1BIN["bin/black"]
BIN -->|symlink or copy| V1BIN
classDef input fill:#3f72af,stroke:#28517f,color:#fff
classDef proc fill:#2a9d8f,stroke:#1c6b61,color:#fff
classDef out fill:#388e3c,stroke:#256128,color:#fff
classDef venv fill:#7c4dff,stroke:#5a2fd0,color:#fff
classDef shared fill:#c78c20,stroke:#8a6011,color:#fff
class HOME input
class DATA,VENVS proc
class BIN out
class SHARED shared
class V1,V2,V3,V1BIN venv
You can do all of this yourself; pipx automates it. Pass --verbose to see each command it runs and to stream
installer output.
Resolving the Python interpreter¶
pipx prefers a real system Python and only downloads a standalone build as a fallback. When you pass --python, it
tries the value as a literal path, then as a command on PATH, then as a pythonX.Y command, then through the
py launcher on Windows. Only if every step fails, and --fetch-python permits it, does pipx fetch a build from
python-build-standalone. The default, --fetch-python=never,
keeps pipx offline and errors out instead.
For the authoritative step-by-step order, the --fetch-python values, and when a downloaded build beats a patched
system Python, see Standalone Python.
flowchart TD
S["--python VALUE"] --> A{"file exists?"}
A -- yes --> USE["use interpreter"]
A -- no --> B{"on PATH?<br/>shutil.which"}
B -- yes --> USE
B -- no --> C{"pythonX.Y?<br/>(non-Windows)"}
C -- yes --> USE
C -- no --> D{"py launcher?<br/>(Windows)"}
D -- yes --> USE
D -- no --> E{"--fetch-python<br/>allows download?"}
E -- "missing / always" --> DL["download standalone build"]
E -- "never (default)" --> ERR["error"]
classDef input fill:#3f72af,stroke:#28517f,color:#fff
classDef decision fill:#c78c20,stroke:#8a6011,color:#fff
classDef out fill:#388e3c,stroke:#256128,color:#fff
classDef err fill:#b3261e,stroke:#7a1a15,color:#fff
class S input
class A,B,C,D,E decision
class USE,DL out
class ERR err