From devpi
devpi combines a caching pypi.org mirror with user-owned indexes, index inheritance, a web UI, primary/replica replication, and a pluggy-based extension model. Its server guide documents SQLite as the default storage and PostgreSQL through a plugin. peryx covers the corresponding cache and hosted-index flows in one process.
Comparison against peryx
Overlap
Both are read-through pypi.org mirrors that cache what they fetch and host private uploads. Their caching behavior overlaps in these areas:
- Read-through mirroring of pypi.org, as covered by devpi's mirror guide, cached on first use.
- Private uploads through the devpi package workflow, served from the same host as the cached index.
- Composition: devpi's index inheritance
(
bases) maps onto peryx's virtual indexes. The default PyPI mode unions distinct filenames; configure project isolation when migrating a private name boundary. - Yank and delete of hosted files.
- A web UI for browsing packages (devpi-web; built into peryx at
/). - Streaming artifact downloads: devpi's
FileStreamerand peryx both tee a wheel to disk while the client reads it, and both address stored files by sha256.
devpi-only behavior
Migrating to peryx changes these areas:
- User-owned indexes. devpi indexes belong to users and carry
acl_upload. peryx indexes belong to configuration. Artifact clients use configured or managed scoped tokens; server users and external groups use role grants for management access. - Replication protocol. devpi replicas consume
the primary's changelog and relay mutations to the primary. peryx selects
dcorhathrough[availability]and uses its own journal, frontier, placement, and authority contracts. No devpi replication state migrates. - Promotion (
push). devpi can push a release between indexes. In peryx, publish the release to the destination index.
peryx-only behavior
- PEP 658 metadata by default. The current devpi server keeps core metadata
experimental behind
--enable-core-metadata. peryx serves it without a feature switch and synthesizes it with HTTP byte-range reads when an upstream lacks it, so resolution can beat the upstream once metadata is cached. - Correctness under a concurrent cold burst. On the first parallel fetch of a project, devpi can evaluate the
request against an as-yet-empty project list, return a
404, and cache that "does not exist" for its 30-minute mirror window; uv then fails the install. peryx single-flights concurrent misses onto one upstream fetch, so ten cold installs of the same project all succeed. - Built-in observability. Prometheus metrics and per-file usage counters are part of the server, not plugin territory.
- One executable. The same binary serves indexes and contains the
none,dc, andhaavailability implementations. It needs nodevpi-initstep or external database.
Performance vs peryx
The benchmark suite runs both servers from their published packages against the same workload. Cold and warm installs through uv:
| peryx | devpi | |
|---|---|---|
| cold cache net | 4.4 s ±6% (1.18x) | 12.7 s ±12% (3.42x) |
| warm cache | 3.3 s ±1% (0.90x) | 4.3 s ±40% (1.19x) |
| server CPU | 1.8 s ±4% (1.00x) | 17.2 s ±15% (9.71x) |
| server peak memory | 699 MB ±2% (1.00x) | 1,373 MB ±2% (1.96x) |
The parallel-install workload is where the concurrency difference shows up: ten virtualenvs install the same project at once, each with an empty client cache.
| peryx | devpi | |
|---|---|---|
| cold cache: 10 parallel installs net | 1.5 s ±11% (0.33x) | error (n/a) |
| warm cache: 10 parallel installs | 1.5 s ±1% (0.32x) | error (n/a) |
| server CPU | 673 ms ±7% (1.00x) | 247 ms ±109% (0.37x) |
| server peak memory | 182 MB ±8% (1.00x) | 1,175 MB ±0% (6.45x) |
The request workload drives a swarm of resolvers reading full project pages:
| peryx | devpi | |
|---|---|---|
| 1 user: requests/s | 7,658 req/s ±2% (128.49x) | 107 req/s ±0% (1.80x) |
| 1 user: p95 latency | 2.3 ms ±2% (0.06x) | 43 ms ±1% (1.10x) |
| 32 users: requests/s | 17,707 req/s ±5% (23.84x) | 114 req/s ±1% (0.15x) |
| 32 users: p95 latency | 3.8 ms ±6% (0.04x) | 307 ms ±2% (3.31x) |
| server CPU per 1k requests | 168 ms ±1% (1.00x) | 9.6 s ±1% (57.16x) |
| server peak memory | 108 MB ±6% (1.00x) | 1,193 MB ±0% (11.02x) |
Migration procedure
devpi's mirror state does not migrate and does not need to: peryx's cache refills on first use. Only your uploaded
packages need a twine upload pass into the new hosted index. Map the commands and knobs across:
| devpi | peryx |
|---|---|
devpi-init then devpi-server --port 3141 | peryx serve |
http://host:3141/{user}/{index}/+simple/ | http://host:4433/{route}/simple/ |
devpi index -c dev bases=root/pypi | Virtual index with layers = ["dev-hosted", "pypi"] |
devpi login and devpi upload | twine upload --repository-url http://host:4433/{route}/ dist/* |
devpi remove pkg==1.0 | DELETE /{route}/{project}/{version}/ |
volatile=False | volatile = false on the hosted index |
mirror_whitelist | Explicit fallback_mode and protected_names source policy |
acl_upload | configured or managed scoped tokens with write |
| devpi-web plugin | Built in at / |
| primary/replica options | [availability] with mode = "dc" or mode = "ha" |
Gotchas
- Artifact ACLs become scoped tokens. Use configured access grants or managed scoped tokens for principals that must retain distinct read, write, or delete permissions. Role grants cover management operations, not artifact requests.
- Replication configuration must be rebuilt. Configure peryx membership and roles; do not copy devpi changelog or replica state.