Creating and using a wheelhouse for offline Python installs
A wheelhouse is a pre-built directory of wheels created on a network-connected build machine and installed offline on a target machine. Pin a requirements file, run pip wheel, archive with tar, then install with --no-index --no-deps --force-reinstall.
On this page
The short answer
A wheelhouse is a directory of pre-built wheel files that you create on a machine with network access and then transfer to an isolated target machine. The build machine reads a pinned requirements file, runs pip wheel with --wheel-dir to compile every dependency, and packages the directory with tar. On the target machine, you extract the archive and run pip install with --no-index --no-deps --force-reinstall so pip never contacts PyPI and installs only the bundled wheels. Hash-checking can be added by including --hash entries in the requirements file, which verifies package integrity during the build step. This method is appropriate when the target has no network access, you want to avoid recompiling on the target, and you can control the OS and architecture. It is not a substitute for a private index because the archive is a static snapshot, is typically OS and architecture-specific, and does not provide ongoing availability or access control.
Building the wheelhouse with pip wheel
The core workflow starts with a pinned requirements file. Run python -m pip wheel -r requirements.txt --wheel-dir=/tmp/wheelhouse to compile every dependency into a wheel and store it in the wheelhouse directory. The build machine performs the compilation, so the target machine does not need a compiler or build tools. After the command finishes, the wheelhouse directory contains one wheel file per package, including transitive dependencies. The next step is to archive that directory so it can be transferred. On a modern Unix system, run tar -cjvf wheelhouse.tar.bz2 -C /tmp/wheelhouse . to create a single compressed archive. The archive is what you ship to the isolated environment. The requirements file is the input to this step, so it must be exact and complete before building.
mkdir -p /tmp/wheelhouse && python -m pip wheel -r requirements.txt --wheel-dir=/tmp/wheelhouse && tar -cjvf wheelhouse.tar.bz2 -C /tmp/wheelhouse .Practical tip
Generate the requirements file with pip freeze on the build machine, then verify the pinned file contains every transitive dependency before running pip wheel. This prevents missing packages from causing a failed offline install.
Installing from the wheelhouse without network access
On the target machine, extract the archive into a temporary directory, then run pip install with three important flags. --no-index tells pip not to contact any package index, so it cannot fall back to PyPI. --no-deps prevents pip from resolving or installing dependencies outside the bundle, which is safe because the requirements file already lists every package. --force-reinstall ensures the bundled wheels are installed even if a matching version is already present. The command python -m pip install --force-reinstall --no-index --no-deps /tmp/wheelhouse/* installs all wheels from the extracted directory. This sequence achieves true offline installation because no network request is made during the install step. The requirements file must already contain every transitive dependency, otherwise --no-deps will leave the environment incomplete.
tar -xvf wheelhouse.tar.bz2 -C /tmp/wheelhouse && python -m pip install --force-reinstall --no-index --no-deps /tmp/wheelhouse/*Preparing a pinned requirements file
A wheelhouse is built from a requirements file with exact version pins. You can generate this file with pip freeze, which records the packages installed in the current environment, including transitive dependencies, not only top-level packages. Each line uses the == operator to require a specific version, for example SomePackage == 1.2.3. Pinning protects you from bugs or incompatibilities in newly released versions because pip will not upgrade anything during the build or install steps. The requirements file is the input to the wheel-building step, so it must be complete and accurate before you run pip wheel. If the file is missing a transitive dependency, the offline install will fail because --no-deps prevents pip from fetching it from an index. Review the generated file to ensure it includes every transitive dependency before running pip wheel.
Archiving the wheelhouse for transfer
Once the wheel directory is populated, convert it into a single portable archive using tar. The command tar -cjvf wheelhouse.tar.bz2 -C /tmp/wheelhouse . creates a compressed tar archive containing all wheel files. This archive is what gets transferred to the isolated environment. The archive is a static snapshot of the build machine state, so it preserves the exact compiled packages and their versions. It does not include build scripts or source code, only the ready-to-install wheels. The archive must be transferred to the target machine before installation can begin. Because the archive is a single file, it is easy to move across machines, but it is not portable across different operating systems or CPU architectures.
Adding hash verification to the wheelhouse workflow
Hash-checking can be combined with the wheelhouse method because both use a requirements file. You add --hash entries to the requirements file, for example FooProject == 1.2 --hash=sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824. These hashes are verified when pip wheel downloads packages, which guards against a compromise of the source index or the HTTPS certificate chain. It also guards against a package changing without its version number changing on indexes that allow this. This is an integrity layer on top of the offline bundle. The hashes must be valid and match the packages in the archive. If the hashes do not match, pip will refuse to use the package, which protects the build from silent changes. Hash-checking mode is an integrity layer, not a labour-saving alternative to running a private index server, because it does not provide the availability benefits of a private index or a vendored library.
Declaring build dependencies in pyproject.toml
The [build-system] table in pyproject.toml declares Python level dependencies that must be installed to run the project's build system. The requires key lists those dependencies, for example requires = [setuptools]. These build-time requirements influence what pip wheel compiles because the build backend needs them to create the wheel. Understanding this table helps ensure the wheelhouse captures all necessary build-tool dependencies for compilation to succeed on the build machine. If the build system requires additional packages, they should be listed in requires so the build machine can install them before running pip wheel. The default semantics apply when a pyproject.toml file is not present, but an explicit table makes the build requirements clear and reproducible.
Portability limits and when a wheelhouse is not enough
A wheelhouse contains compiled packages that are typically OS and architecture-specific, so archives are not necessarily portable across machines. The same wheel will not work on a different operating system or CPU architecture without recompilation. This method also requires a build machine with network access; it does not help if no machine can reach PyPI. A wheelhouse does not substitute for a private index when you need cross-platform support or a living audit trail beyond a static archive. Hash-checking alone provides integrity but not availability, and a wheelhouse provides availability only for the exact packages and platforms it contains. If you need to support multiple platforms, you must build separate wheelhouses for each target environment. The archive is a snapshot, not a service, so it cannot provide ongoing availability or access control.
When a wheelhouse is the right tool
A wheelhouse is the right tool when the target environment has no network access to PyPI, you want to avoid time-consuming recompilation on the target machine, and you can control the OS and architecture. It packages all compilation work into a single archive, which is distinct from merely pinning versions or using hash-checking alone. Pinning protects against new version bugs, and hash-checking adds integrity, but neither provides the availability or offline installation convenience of a wheelhouse. The wheelhouse is a bundle of pre-built wheels that the target machine installs without contacting any index. This approach works across OSes and architectures only when the build and target machines share the same platform. It is a practical solution for isolated deployments where network access is unavailable or restricted.
Things to check
- The requirements file must pin every package with == and include transitive dependencies, not only top-level packages.
- The build machine must have network access to download and compile all dependencies before archiving.
- The target machine must use the same OS and CPU architecture as the build machine for the wheels to be compatible.
- The installation command must include --no-index, --no-deps, and --force-reinstall to prevent network access and external dependency resolution.
- The archive must be created from the wheel directory with tar before transfer, and extracted before installation.
- Hash-checking entries in the requirements file must be valid and match the packages in the archive.
- The wheelhouse contains compiled packages and is not portable across different operating systems or architectures.
- A wheelhouse does not replace a private index for ongoing availability, access control, or audit management.
Where this applies
A wheelhouse contains compiled packages that are typically OS and architecture-specific, so archives are not necessarily portable across machines. The approach requires a build machine with network access; it does not help if no machine can reach PyPI. Compiled wheels may contain platform-specific binary code, so cross-compilation is not supported by this method alone. The archive is a static snapshot and does not provide ongoing availability or ACL management like a private index server. Using --no-deps means the operator must ensure the requirements file already lists every transitive dependency.