Documentation

Cache packages for CI

CI jobs often rebuild environments and download the same wheels. Run peryx near the runners and point installers at it. The first job warms the cache; later jobs install from local disk.

Run peryx next to the runners

On the CI host, or as a service in the runner network:

peryx serve --host 0.0.0.0 --port 4433 --data-dir /var/lib/peryx

The data directory is the cache. Mount it on a persistent volume.

In Kubernetes or docker-compose, run one container with the binary and data volume. peryx needs no database or sidecar.

Point the installers at it

Set the index environment variable at the runner or organization level:

export UV_INDEX_URL=http://peryx.internal:4433/root/pypi/simple/
export PIP_INDEX_URL=http://peryx.internal:4433/root/pypi/simple/
# pyproject.toml, for uv-managed projects
[[tool.uv.index]]
url = "http://peryx.internal:4433/root/pypi/simple/"
default = true

Jobs that pass --index-url can use the same URL.

Docker builds

Builds inside docker build do not see the host network by default. Either pass the index through a build argument:

ARG PIP_INDEX_URL
RUN pip install -r requirements.txt
docker build --build-arg PIP_INDEX_URL=http://peryx.internal:4433/root/pypi/simple/ .

You can also run the build on a network where peryx.internal resolves by using --network with BuildKit. BuildKit cache mounts serve one machine; peryx shares artifacts across machines, tags, and projects.

Inspect usage and storage

Run several jobs, then inspect cache usage:

curl -s -u operator:"$OPERATOR_PASSWORD" 'http://peryx.internal:4433/+stats?index=root/pypi' | jq .totals

downloads and bytes count what peryx served; once the working set is warm, upstream traffic drops to page revalidations (refreshes, mostly 304s with no body). The dashboard shows the same numbers with per-project drill-down, and /metrics feeds Prometheus.

The cache CLI reads the shared content store and scopes metadata commands by the PyPI route:

peryx cache size --data-dir /var/lib/peryx
peryx cache list --data-dir /var/lib/peryx --index root/pypi --stale
peryx cache fsck --data-dir /var/lib/peryx

Purge uses a plan first. Add --yes after checking its row counts:

peryx cache purge project --data-dir /var/lib/peryx --index root/pypi --project flask
peryx cache purge project --data-dir /var/lib/peryx --index root/pypi --project flask --yes
peryx cache purge orphaned-blobs --data-dir /var/lib/peryx
peryx cache purge orphaned-blobs --data-dir /var/lib/peryx --yes

Project purge removes metadata rows and leaves shared blobs in place. Orphaned-blob purge removes content with no metadata reference. Rebuild the derived package search after a metadata restore with peryx job reindex.

Cache behavior

  • Wheels are immutable and content-addressed. Each wheel crosses the uplink once (architecture).
  • Cold misses stream through at upstream speed (measurements).
  • During a pypi.org outage, peryx serves stale pages and artifacts from disk.
On this page