2025, Nov 01 03:00

Offline pip installs on air-gapped servers: avoid build isolation failures and the setuptools not found error

Learn why pip fails with Could not find setuptools on air-gapped hosts and fix it using local wheels (--find-links) or --no-build-isolation for offline installs

Air‑gapped or locked‑down servers are great for security, but they expose a sharp edge in Python packaging. You may have all runtime dependencies preinstalled, drop in your own source distribution, and still watch pip fail with a complaint that it can’t find setuptools—even though importing it in Python works. The root cause is build isolation, and the good news is that you can fix this cleanly without opening network access.

Reproducing the issue

On an offline machine, installing a local sdist with pip while blocking any remote index might look like this:

pip3 install --no-deps --no-index acme_pkg-1.0.0.tar.gz

The installation aborts with a message similar to:

× pip subprocess to install build dependencies did not run successfully.
│ exit code: 1
╰─> [2 lines of output]
    ERROR: Could not find a version that satisfies the requirement setuptools>=57 (from versions: none)
    ERROR: No matching distribution found for setuptools>=57

Inside Python, however, importing setuptools works and reports a compliant version. That’s the confusing part.

What’s really happening

When pip builds a package from source, it uses build Isolation. It spins up a temporary virtual environment, installs declared build requirements there, builds your package, and then discards that environment. Your system’s global setuptools in /usr/lib/python3/dist-packages doesn’t participate in this isolated step. Because you passed --no-index, pip refuses to reach out to any repository to fetch build dependencies for that ephemeral environment. Without a local wheel it can install into that sandbox, the build fails with “No matching distribution found for setuptools.”

Two proven ways to fix it

The first approach is to prefetch the build tools on a connected machine and make them available locally to the offline host. On a machine with internet access, download the necessary wheels:

pip download setuptools wheel

Copy the downloaded .whl files to the offline server and install your package while pointing pip to that local directory. Keep the index disabled so nothing tries to escape to the network:

pip install --find-links /srv/wheels/ --no-index acme_pkg

The second approach is to bypass the transient environment entirely. If you know the target system already has what it needs, you can instruct pip to avoid build isolation. This keeps the build inside the existing environment, using the already installed setuptools:

pip install --no-index --no-build-isolation acme_pkg

If there’s any doubt about which Python interpreter your pip belongs to, invoke pip through the interpreter to avoid mismatches:

python -m pip install --no-index --no-build-isolation acme_pkg

Why this matters

In restricted environments, deterministic installs are critical. Build isolation is a sensible default for connected systems, but offline it becomes a trap if you don’t seed the build dependencies. Understanding that pip builds inside a short‑lived environment explains why an import succeeds while the installer claims a dependency is missing. Once you either supply local wheels via --find-links or switch off build isolation, the workflow becomes predictable again.

Conclusion

On air‑gapped hosts, prepare for builds. Either mirror the required build wheels such as setuptools and wheel on a connected machine and point pip to that stash with --find-links and --no-index, or disable build isolation when you control the environment and know the necessary tooling is already installed. To eliminate interpreter ambiguity, route all installations through python -m pip. With these adjustments, installing your own package from a local .tar.gz proceeds smoothly without compromising the network policy.

The article is based on a question from StackOverflow by Bill Shubert and an answer by phd.