OrbitalReg
Sign in →

Registry formats · PyPI

A private PyPI server that never mixes your packages with strangers'.

Upload your wheels with twine, proxy and cache pypi.org, and give pip, uv and Poetry one index URL for both. Internal package names can't be shadowed from the public index, brand-new releases can wait a day before they reach a build, and every distribution is matched against advisories, in the same self-hosted registry that holds your images, npm and Maven packages.

Try it free for 30 days → Read the PyPI docs ↗

One index URL

Upload with twine. Install with anything.

Every repository has an upload URL for twine (…/legacy/) and a simple index for installers (…/simple/). Point installers at a virtual repo that federates your local repo with a cached pypi.org remote, and one URL covers internal and public packages.

Coming from devpi or pypiserver? Same wire protocol, so the client side of the migration is an index-url change.

# ~/.config/pip/pip.conf: one index for everything
[global]
index-url = https://__token__:$ORBITALREG_TOKEN@registry.example.com/pypi/acme/all-libs/simple/

# uv reads the same index
$ export UV_DEFAULT_INDEX=https://registry.example.com/pypi/acme/all-libs/simple/
# publish: wheel and sdist into the local repo
$ twine upload \
    --repository-url https://registry.example.com/pypi/acme/internal/legacy/ \
    -u __token__ -p $ORBITALREG_TOKEN \
    dist/*.whl dist/*.tar.gz

The --extra-index-url trap

pip takes the highest version it finds. From any index.

With --extra-index-url, pip treats your private index and pypi.org as equals. Publish a package on pypi.org under one of your internal names with a higher version number, and pip installs the public one. In December 2022 that is how the PyTorch nightly dependency torchtriton was hijacked.

In an OrbitalReg virtual repository, the moment a local member holds files for a name, pypi.org is no longer consulted for that name. Public packages still flow through; your names can't be taken over. The dependency confusion case study has the full story.

# two indexes, one internal name, attacker publishes 9.9.9 on pypi.org
$ pip install acme-auth \
    --index-url https://pypi.internal.example.com/simple/ \
    --extra-index-url https://pypi.org/simple/
  internal  acme-auth 1.4.2
  pypi.org  acme-auth 9.9.9   ← highest version wins
Installing acme-auth 9.9.9
# one index: virtual repo = local "internal" + remote "pypi-org"
$ pip install acme-auth \
    --index-url https://registry.example.com/pypi/acme/all-libs/simple/
  internal  acme-auth 1.4.2   ← local member holds the name
  pypi.org  not consulted for acme-auth
Installing acme-auth 1.4.2

What you get

A private PyPI server with the security layer in the free version.

The simple index pip and uv expect

PEP 503 HTML and PEP 691 JSON from the same URL, with #sha256 fragments on every file so pip --require-hashes and uv lockfiles verify what they download. PEP 658 core metadata lets resolvers read a wheel's dependencies without downloading the wheel, and Requires-Python is passed through.

Wheels and sdists, metadata parsed

twine upload takes .whl and .tar.gz distributions. Metadata comes from the wheel's dist-info/METADATA or the sdist's PKG-INFO, so names, versions and dependencies are indexed exactly as PyPI would index them.

Release-hold for pypi.org

Versions younger than N hours are refused, measured from the upload time pypi.org reports. Held versions also drop out of the simple index, so pip install picks the previous release instead of failing. New installs start with a 24-hour global hold, and a security fast-track lets fixes through early.

Safe versions in the simple index

Turn on safe-version for a virtual repo and versions an advisory definitely affects disappear from the index when a clean replacement exists in the allowed range, including the index served from pypi.org. requests>=2.31 then resolves to the clean release. A pinned version is never swapped for different bytes.

Every distribution checked against advisories

Uploaded and cached files alike get an SBOM built from their metadata, matched against OSV advisories. Trivy and Grype add deep scans for the projects you enable them on, and findings above a threshold can quarantine the version automatically.

Block a package everywhere at once

Security blocks match a package name, a version pattern or the exact SHA-256 of a file, on every download path including the pypi.org cache. A developer whose install was refused gets a page with your explanation instead of a bare error.

A pull-gate in front of pip install

Optional and off by default: a policy engine evaluated before every download can block on maximum CVE severity, licence allow- or deny-lists, a missing SBOM, or your own Rego rules, and answers CI runners with a 403 whose X-Orbital-PullGate-* headers explain the reason.

No long-lived tokens in CI

twine can upload with a short-lived OrbitalReg token exchanged from a GitLab, GitHub or Kubernetes OIDC identity, so no publishing token sits in CI variables (a licensed feature). Every download is recorded with token, client IP and user agent.

Why not a Python-only index

Python packages rarely ship alone.

devpi and pypiserver are good, lightweight answers to "host our private wheels". What they don't do is check what they cache against advisories, hold back a release that went up an hour ago, or block a known-malicious package by name or hash. They also only hold Python: the container image your service runs in, the npm frontend next to it and the Helm chart that deploys it all live somewhere else, under a different policy. In OrbitalReg the PyPI adapter is one GA format among 40+, governed by the same projects, roles, blocks, pull-gate and append-only audit log.

PyPI is also where LLMs invent package names that attackers then register: see slopsquatting. For your other ecosystems, see the npm, Maven and container registry pages, and the pricing page for what is free.

Questions

Private PyPI servers, answered.

How do I point pip at a private PyPI server?

Set index-url in pip.conf (or PIP_INDEX_URL) to a virtual repository's simple index, https://<host>/pypi/<project>/<repo>/simple/, with __token__ as the username and an OrbitalReg token as the password. Use only that one index; the virtual repository brings in pypi.org itself.

Why not just use --extra-index-url?

Because pip treats every index as equal and installs the highest version it finds anywhere. If someone publishes a package on pypi.org with the name of one of your internal packages and a higher version number, pip takes the public one. That is how the PyTorch nightly dependency torchtriton was hijacked in December 2022. A single index URL pointing at a virtual repository avoids it: once a local member holds files for a name, pypi.org is never consulted for that name.

Do uv, Poetry and pipenv work too?

Yes. They read the same simple index, so you set the same URL as their default index (for uv, UV_DEFAULT_INDEX or --default-index). uv benefits most from PEP 658 metadata and PEP 691 JSON, both of which OrbitalReg serves.

Won't a release-hold delay urgent security fixes?

Not when the security fast-track is on: a version that an OSV or GHSA advisory lists as the fix for an older, released version is let through early. Admins can also approve a single held release by hand. Your own uploads are never held.

How do we migrate from devpi, pypiserver or Artifactory?

devpi and pypiserver speak the same simple-index protocol, so the client side is an index-url change; re-upload your distributions with twine. From Artifactory or Nexus, the orbital migrate CLI imports PyPI repositories along with the other formats (a commercial-licence feature, available during the 30-day trial).

Can OrbitalReg stop hallucinated or typosquatted packages?

It narrows the window. Release-hold keeps brand-new names and versions out for the first hours, when many malicious uploads are spotted and removed from pypi.org. Security blocks stop a known-bad name for every developer at once, and the pull audit shows who installed it before the block.

Documentation: PyPI format reference · pull-gate and release-hold · safe-version delivery · format support matrix

Free forever · no contract

Run your own PyPI server in your cluster today.

One Helm chart, one Go binary, Postgres and S3-compatible storage. Advisory matching, release-hold, safe versions and the pull-gate are part of the free version.