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 creates the venv without pip, then creates or reuses a shared environment that holds an up-to-date pip and links it into the 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.

pipx refreshes the shared pip once it is more than 30 days old, or right away when it is older than pip 26.2.2; pipx upgrade-shared refreshes it immediately. When pip’s files are missing from the shared environment, pipx rebuilds it before running pip. It also rebuilds shared environments created with Python older than 3.10, which cannot install the required pip version. Rebuilding uses the default Python interpreter; existing applications’ .pth references are refreshed before their next installer operation. When a pip command fails because pip no longer imports, pipx rebuilds it and retries the command once.

After installing the package, pipx exposes the application’s resources so you can reach them from any directory. It handles three 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 it copies the file instead when the target directory does not support symlinks. 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. When an upgrade or reinstall changes the resources a package ships, pipx exposes the new ones and deletes the links or copies of the ones the package dropped.

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 --> C["create venv<br/>venvs/black/"]
    BK -- uv --> U["uv venv<br/>(no pip)"]
    C --> P["link shared pip<br/>via .pth"]
    P --> D["install package<br/>pip install / uv pip install"]
    U --> D
    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. Under the pip backend pipx either reuses a cached temporary venv or builds a fresh one, then invokes the app.

The cache key is a hash of the requirements (the spec plus any --with packages, with local paths made absolute), the --python value, pip and venv arguments, the backend, the cooldown, and the installer’s environment variables (PIP_*, or UV_* under the uv backend, in any case and with - or _, as pip reads them). pipx leaves out a short list of variables that cannot change what gets installed, such as timeouts, retries, verbosity, progress bars, color and cache directories, so they do not fork the cache.

The key also covers the variables outside those prefixes that pip or uv read, in any case:

  • config files: XDG_CONFIG_HOME, XDG_CONFIG_DIRS, APPDATA, SYSTEMDRIVE, WIN_PD_OVERRIDE_APPDATA and WIN_PD_OVERRIDE_COMMON_APPDATA

  • TLS trust: SSL_CERT_FILE, SSL_CERT_DIR, SSL_CLIENT_CERT, REQUESTS_CA_BUNDLE and CURL_CA_BUNDLE

  • proxies: HTTP_PROXY, HTTPS_PROXY, ALL_PROXY and NO_PROXY

  • credentials: NETRC, PYTHON_KEYRING_BACKEND, HF_TOKEN, PYX_API_KEY, PYX_AUTH_TOKEN and the AWS_* variables uv reads to sign S3 requests

HOME and USERPROFILE stay out: another home means another user, whose default pipx home, and so its cache, already differs. The values only feed the hash, so tokens and proxy passwords never appear in a cache path or a log line.

The hash is an HMAC keyed with a random secret that pipx creates on the first run and keeps in .run-key inside the cache directory. Other accounts can often list that directory, and without the secret they could hash guesses at a token until one matched an entry’s name. On POSIX pipx creates the file with mode 0600 and replaces one that another account owns or can read. On Windows it relies on the ACLs of the user profile. When pipx can neither read nor write the secret, as in a read-only cache, it falls back to a plain SHA-256 hash and logs this at debug level.

Cached environments expire after 14 days, after which the next run rebuilds against the latest release. The key leaves out edits to pip.conf or uv.toml, so pass --refresh after changing them. The temporary venv borrows the shared pip.

Under the uv backend pipx usually hands the whole run to uv tool run, which uses uv’s own cache. pipx run --with PKG adds extra dependencies to the temporary environment either way.

        flowchart LR
    A["pipx run pycowsay"] --> BK{"backend?"}
    BK -- pip --> C{"cached<br/>venv?"}
    BK -- uv --> U["uv tool run<br/>(uv's cache)"]
    C -- yes --> E["reuse cached venv"]
    C -- no --> P["python -m venv +<br/>shared pip"]
    P --> D["install pycowsay"]
    D --> F["invoke app"]
    E --> F
    U --> 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 venvs and shared libraries under one home directory; the run cache and logs use the platform cache and log directories unless PIPX_HOME is set. 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 when one is on PATH. Outside Windows pipx asks the launcher only for an X or X.Y version, the one form the Python Launcher for Unix reads as a version request. Only if every step fails, and --fetch-python=missing is set, does pipx fetch a build from python-build-standalone. --fetch-python=always skips these steps and downloads a build. The default, --fetch-python=never, keeps pipx offline and errors out instead.

For the full 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"] --> AL{"--fetch-python<br/>=always?"}
    AL -- yes --> DL["download standalone build"]
    AL -- no --> 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?"}
    D -- yes --> USE
    D -- no --> E{"--fetch-python<br/>=missing?"}
    E -- "yes" --> DL
    E -- "no (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 AL,A,B,C,D,E decision
    class USE,DL out
    class ERR err