Documentation

Getting started

In this tutorial you point peryx at pypi.org, install packages through it with pip and uv, publish a package of your own to a private hosted store, then yank and delete it. It takes about ten minutes.

A Python package ships as one or both of two artifacts: a wheel (.whl), a pre-built archive an installer unpacks as-is, and an sdist (.tar.gz), the source a wheel is built from. Installers find them through the Simple API, the HTTP protocol peryx speaks to pip, uv, and twine.

Prerequisites

You need Python with pip or uv as the client, and a peryx binary. Pick whichever install channel fits; installation lists them all:

# standalone binary, no Python involved
curl -LsSf https://github.com/tox-dev/peryx/releases/latest/download/peryx-installer.sh | sh
uv tool install peryx
pip install peryx
# needs a Rust toolchain (https://rustup.rs); rust-toolchain.toml pins the version
git clone https://github.com/tox-dev/peryx.git
cd peryx
cargo build --release

Start peryx

The read path needs no configuration. The defaults give you a pypi.org cached index with a private hosted store, combined by a virtual index in front of them, served at route root/pypi:

peryx serve            # ./target/release/peryx serve when built from source

peryx is now listening on 127.0.0.1:4433. Leave it running and use a second terminal for the rest of the tutorial.

Install through the cache

Point any installer at the index URL. The first install fetches from pypi.org, verifies each artifact against its sha256, and caches it; repeat installs come from disk:

uv venv demo
VIRTUAL_ENV=demo uv pip install --index-url http://127.0.0.1:4433/root/pypi/simple/ requests
python -m venv demo
demo/bin/pip install --index-url http://127.0.0.1:4433/root/pypi/simple/ requests

Both clients use the PEP 658 metadata fast path through peryx: they resolve dependency trees by fetching small .metadata files rather than whole wheels. You can watch it on the metrics endpoint:

curl -s http://127.0.0.1:4433/metrics | grep metadata

Publish a private package

Uploads are disabled until you set a token. Stop peryx, write a config that adds one, and restart:

# peryx.toml
[[index]] # cached: read-through cache of pypi.org
name = "pypi"
cached = "https://pypi.org/simple/"

[[index]] # hosted: your own packages, upload needs the token
name = "hosted"
upload_token = "demo-secret"

[[index]] # virtual: your uploads shadow upstream behind one URL
name = "root/pypi"
layers = ["hosted", "pypi"]
upload = "hosted"
peryx serve --config peryx.toml

Now publish a wheel with twine or uv (any username; the token is the password):

uv publish --publish-url http://127.0.0.1:4433/root/pypi/ -u __token__ -p demo-secret dist/*

Your package installs from the same URL as everything else: the virtual index serves your upload and pypi.org side by side, and your file shadows any upstream file with the same name.

Yank and delete it

Mark a version yanked (PEP 592) so resolvers skip it while pinned installs still work, then delete it outright:

curl -X PUT    -u __token__:demo-secret http://127.0.0.1:4433/root/pypi/mypkg/1.0.0/yank
curl -X DELETE -u __token__:demo-secret http://127.0.0.1:4433/root/pypi/mypkg/

The same actions live in the web UI: open the project page, expand "Manage uploads", and enter the token. After the delete, the upstream version of mypkg (if pypi.org has one) is visible again.

Verify

peryx serves a web interface on the same port. Open http://127.0.0.1:4433/ for a live dashboard of the configured indexes and request counters, click an index for a searchable project list, and click a project for a pypi.org-style page: description, dependencies, classifiers, and files with hashes. The same counters are JSON at /+stats and Prometheus at /metrics.

Where next

On this page