What does pip install --target do, and how do those packages end up on sys.path?
answer
- Install into a plain directory
- No virtual environment is created
- Import still has to find it
- PYTHONPATH or an explicit sys.path insert
- Rebuild from empty, never upgrade in place
basics
~10 spip install --target DIR unpacks distributions into DIR instead of an environment's site-packages. Nothing adds DIR to sys.path for you: the program supplies it through PYTHONPATH or an explicit sys.path insertion.
solid answer
~50 s`pip install --target ./vendor -r requirements.txt` writes each distribution's top-level packages plus its `.dist-info` directory into `./vendor` as plain files. There is no virtual environment involved — no `pyvenv.cfg`, no activation, no `site-packages` machinery — so import has to be told the directory exists: set `PYTHONPATH=./vendor`, insert the path into `sys.path` early in the entry module, or place the tree where the runtime already looks, such as beside the `__main__.py` inside a zipapp. That makes `--target` the standard way to vendor dependencies for a delivery shape that has no environment of its own. The caveats matter: the directory is unmanaged, so re-running the command overwrites what it installs and leaves stale packages behind — rebuild from empty rather than upgrade in place — and cross-platform vendoring needs `--platform` together with `--only-binary=:all:`, which pip accepts only alongside `--target`.
code
console · 3 linesrm -rf ./vendor
python -m pip install --target ./vendor -r requirements.txt
PYTHONPATH=./vendor python -m myappgo deeper
Recall that the flag installs packages into a directory you name instead of the active environment, and that you still have to tell Python where that directory is before anything imports.
Explain the mechanics: a plain tree of packages plus .dist-info, no pyvenv.cfg, made importable through PYTHONPATH or a sys.path insertion, and used to vendor dependencies for shapes that have no environment.
Demonstrate the operational discipline — rebuild the directory from empty every build, resolve for the target platform rather than the laptop, and keep metadata intact so version and entry-point lookups still work in production.
Own the choice of when hand-vendoring is warranted at all. Argue for it only where the delivery shape forbids an environment, and be explicit about the maintenance cost you are accepting versus an image or a bundled environment.
## What the flag actually does A normal `pip install` resolves a dependency set, downloads or builds wheels, and unpacks them into the `site-packages` directory of whatever environment pip belongs to, registering each one with a `.dist-info` directory. `--target DIR` changes only the destination: the same resolution and the same unpacking happen, but the results land in `DIR`. What you get afterwards is an ordinary directory holding importable top-level packages and modules beside their `.dist-info` metadata — nothing else. Console-script wrappers land inside the target tree rather than anywhere on `PATH`, and there is no `pyvenv.cfg`, no `activate`, and nothing for pip to later uninstall from. ## Why nothing imports until you say so This is the part candidates get wrong. A virtual environment gets onto `sys.path` because the interpreter starting from that environment computes its own `site-packages` and the `site` module adds it. A `--target` directory participates in none of that. It becomes importable only if something puts it on the path: ```console python -m pip install --target ./vendor -r requirements.txt PYTHONPATH=./vendor python -m myapp ``` or by inserting it from inside the program before the first third-party import, or implicitly because the directory *is* `sys.path[0]` — which is exactly what happens when the vendored tree sits at the root of a zipapp archive next to `__main__.py`, or when the process's working directory is the vendor tree. Because the entry lands at the front when you insert it yourself, a vendored package can shadow a same-named module installed on the host, which is sometimes the point and sometimes a subtle bug. ## Where it is the right tool `--target` exists for delivery shapes that have no interpreter environment to install into. Building a single-file archive is one: the dependencies have to be *files in a directory* before they can be zipped. Function-style deploy targets that expect a flat directory of code plus libraries are another. So is a constrained appliance where you copy a tree into place and cannot run a package manager at deploy time. In all of those the pattern is the same: resolve once on a build machine, produce a directory, ship the directory, and let the runtime find it. What it is *not* is a lightweight virtual environment. If the target can create an environment, `python -m venv` plus an install into it is better in every way that matters — pip can then upgrade and uninstall coherently, the environment records its base interpreter, and console scripts work. ## The failure modes **Drift.** Because pip is not managing the directory, running `--target` into a non-empty tree overwrites the files it installs this time and silently leaves behind everything from previous runs, including packages you removed from the requirements and older top-level modules of packages that reorganised. The result is a tree that imports something no lockfile mentions. The discipline is: the vendor directory is a build output, deleted and rebuilt from empty on every build, never patched in place. **Platform.** pip resolves for the interpreter running it. Vendoring on a developer laptop and shipping to a different operating system or CPU gives you wheels that will not import. For a build that must target another platform, pip accepts `--platform`, `--abi` and `--python-version`, but only when the destination is a `--target` directory and the resolution is binary-only (`--only-binary=:all:`, or `--no-deps`), because it cannot run arbitrary builds for a platform it is not on: ```console python -m pip install --target ./vendor \ --platform manylinux_2_28_x86_64 --only-binary=:all: -r requirements.txt ``` Any dependency without a wheel for that tag fails the command outright — which is the useful behaviour, because the alternative is discovering it on the deploy host. **Compiled dependencies plus a zip.** A vendored tree that contains a binary extension module is fine as a directory but cannot be imported from inside a zip archive, so `--target` output destined for a zipapp must be pure Python. **Metadata still works, if you keep it.** `importlib.metadata` reads the `.dist-info` directories, so version lookups and entry-point discovery keep working against a vendored tree as long as the path is on `sys.path` and the `.dist-info` directories were not stripped to save space. Stripping them is a classic self-inflicted wound: the application starts, then fails much later when some library asks for its own version. ## The interview shape of the answer Say what it does (installs into a plain directory), say what it does not do (put that directory on `sys.path`, manage the directory over time, adapt to the target's platform for you), and name the shapes it serves. The strongest version of the answer ends by saying when you would *not* use it: if the target can hold a virtual environment or an image, vendoring by hand is a step backwards.
- When would you vendor with --target instead of creating a virtual environment?When the delivery shape has no environment to install into: building a zipapp, filling a flat code-plus-libraries directory that a runtime unpacks for you, or shipping to a host where no package manager may run at deploy time. If the target can hold a virtual environment or an image, use one — pip can then upgrade and uninstall coherently, console scripts work, and the environment records its base interpreter.
- What goes wrong if a vendor directory is topped up with pip install --target over many releases?It drifts. pip overwrites what it installs this run and leaves everything else untouched, because it is not managing the directory and has no uninstall step there. Removed dependencies, old top-level modules from packages that reorganised, and stale `.dist-info` directories all survive, so the process can import code no lockfile mentions. Treat the directory as a build output: delete it and rebuild from empty every time.
- Why does pip refuse --platform unless you also pass --target and --only-binary=:all:?Resolving for a platform pip is not running on means it cannot execute a source build or trust the environment's own markers, so it must restrict itself to already-built wheels and to a destination that is a plain directory rather than a live environment it would be corrupting. The consequence is useful: a dependency with no wheel for the requested tag fails the build instead of failing on the deploy host.
saying these in an interview costs you the question
- Thinks --target creates a virtual environment
- Assumes the directory joins sys.path automatically
- Tops up the vendor directory instead of rebuilding it
- Vendors wheels built for the build machine's platform
- Strips .dist-info directories to save space
- Believes --target installs console scripts onto PATH