Private Indexes & Alternative Distribution
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the Python Reference bibliography. |
PyPI has no private projects: everything uploaded there is public, permanently. Internal libraries go to a private index, which pip and Poetry use exactly like PyPI because it speaks the same simple repository API (PEP 503/691). This page shows the self-hosted options run on this machine on 2026-10-22 (pypiserver 2.4.2, devpi-server 6.20.3 with devpi-client 7.3.0, pip 26.2.2, all on Linux with CPython 3.14.6) and lists the hosted ones, which are not run here.
What an index needs to be
A package index is just HTTP: /simple/ lists projects, /simple/<normalised-name>/ lists files with their hashes. The simplest
private index is therefore a directory of static files behind any web server:
simple/index.html <a href="birdtally/">birdtally</a>
simple/birdtally/index.html <a href="../../files/birdtally-0.1.0-py3-none-any.whl#sha256=…">…</a>
files/birdtally-0.1.0-py3-none-any.whl
That layout, served with python -m http.server 8085, was enough for pip to install from --index-url
http://127.0.0.1:8085/simple/ (and to honour a data-yanked attribute, see
Managing Published Releases).
Serve it over HTTPS in practice, and generate it with a tool rather than by hand; uploads need a real server.
pypiserver
pypiserver serves a directory of distribution files and accepts uploads. Every local round trip in this section used it:
python -m pip install pypiserver==2.4.2 passlib
htpasswd -sc htpasswd birdwatch # or passlib's HtpasswdFile
pypi-server run -p 8084 -a update -P htpasswd --disable-fallback packages/
-
-a updaterequires authentication for uploads only (-a download,list,updatefor everything);-P htpasswdnames the password file. -
--disable-fallbackmakes it answer 404 for unknown projects. Without it, pypiserver redirects requests for projects it does not have to PyPI, so a supposedly private index silently serves public packages (the trap seen on Repositories & Package Sources). -
Upload with twine (
--repository-url http://localhost:8084/) or Poetry (poetry config repositories.local …); install with--index-url http://localhost:8084/simple/. Both are shown on Publishing with build & twine and Publishing with Poetry. -
A re-upload is refused (
already exists! You may start server with --overwrite option.).
Source: pypiserver.
devpi
devpi adds what pypiserver lacks: users, several indexes, a caching mirror of PyPI (root/pypi) and index inheritance, so one
URL serves private and public packages with the private ones taking precedence:
devpi-init --serverdir devpi-data
devpi-server --serverdir devpi-data --host 127.0.0.1 --port 3141 &
devpi use http://127.0.0.1:3141
devpi user -c alice password="$DEVPI_PASSWORD" email=alice@example.org
devpi login alice --password "$DEVPI_PASSWORD"
devpi index -c dev bases=root/pypi
devpi use alice/dev
devpi upload --from-dir dist
pip install --dry-run --index-url http://127.0.0.1:3141/alice/dev/+simple/ "birdtally[pretty]"
user created: alice
logged in 'alice', credentials valid for 10.00 hours
file_upload of birdtally-0.1.0-py3-none-any.whl to http://127.0.0.1:3141/alice/dev/
file_upload of birdtally-0.1.0.tar.gz to http://127.0.0.1:3141/alice/dev/
…
Would install Pygments-2.21.0 birdtally-0.1.0 markdown-it-py-4.2.0 mdurl-0.1.2 rich-15.0.0
birdtally came from alice/dev, its dependencies from the cached PyPI mirror. PyPI’s own help recommends devpi for private
packages. Once a project exists in alice/dev, devpi stops serving that name from the inherited mirror (unless it is listed in
the index’s mirror_whitelist), which protects uploaded private names; names you have not uploaded yet still resolve from PyPI,
so register internal names early or use an index without a PyPI base for them. Tried here with a private six 0.0.1 uploaded to
alice/dev: pip index versions listed only 0.0.1 there, while root/pypi offered six (1.17.0).
Hosted registries (not run here)
| Registry | Upload and install |
|---|---|
GitLab package registry (all tiers) |
Upload with twine to |
Sonatype Nexus Repository |
A hosted PyPI repository for uploads, a proxy repository for PyPI and a group repository combining them for installs. |
JFrog Artifactory |
Local, remote and virtual PyPI repositories, the same pattern. Its documentation blocks scripted access; read it in a browser. |
AWS CodeArtifact |
|
Azure Artifacts |
A feed’s upload URL is |
Google Artifact Registry |
Python repositories at |
GitHub Packages |
Has no Python (PyPI) registry. Use one of the above, or a GitHub release with wheel files. |
Mirrors and caches
A caching proxy in front of PyPI (devpi’s root/pypi, a Nexus or Artifactory proxy, or a plain HTTP cache) speeds up CI and
keeps builds working when PyPI is unreachable. A full mirror (bandersnatch) copies all of PyPI, which few need. pip’s own cache
already helps per machine (see
pip: Wheels & Caching).
Without an index: Git and direct URLs
For a few internal consumers, a Git repository can be the distribution channel:
python -m pip install "birdtally @ git+https://git.example.org/birds/birdtally.git@v0.2.0"
Limits: every install builds from source (a compiler for extensions, the build backend from an index); there is no version
resolution across releases, only the ref you name; and a package that depends on such a URL cannot be uploaded to PyPI, which
rejects direct references in Requires-Dist. Wheels attached to a GitHub or GitLab release, installed by URL or through
--find-links, avoid the build step. Details are on
pip: VCS, Direct URL & Local Project Installs.
conda-forge is a separate ecosystem with its own recipes, builds and channel; publishing there is a different process (see the conda-forge documentation).
Source: pip: VCS support, PUG: Direct references.
Consuming a private index
-
pip:
--index-url(replace PyPI) rather than--extra-index-url(merge with PyPI, open to dependency confusion); credentials from keyring or.netrc; see pip: Package Indexes & Finding Packages and pip: Authentication, Proxies & Certificates. -
Poetry: an
explicitsource plus asource = "…"constraint on each private dependency; see Repositories & Package Sources.