Skip to content

Setup

Run pypiron from a pypiron.toml, then choose how public packages arrive: proxy on demand, or sync ahead of time.

Create pypiron.toml

Generate the full template when you need it:

pypiron config init > pypiron.toml

Start smaller:

private-prefix = "acme"

[serve]
bind-addr = "0.0.0.0:8080"

Start it:

export PYPIRON_ADMIN_PASS="$ADMIN"
pypiron serve

private-prefix reserves your package names. PYPIRON_ADMIN_PASS enables publishing. ./pypiron.toml is auto-loaded; use --config for another path.

Private packages

Publish to /legacy/, install from /simple/.

uv publish --publish-url http://HOST:8080/legacy/ \
  --username admin --password "$PYPIRON_ADMIN_PASS" dist/*

uv add --default-index http://HOST:8080/simple/ acme-widgets

Add public PyPI

Use the proxy when the server can reach PyPI. Clients keep one index. Cache misses come from PyPI once, then stay local.

private-prefix = "acme"

[serve]
bind-addr = "0.0.0.0:8080"
proxy-upstream = "https://pypi.org"

[mirror]
exclude-newer = "7 days"

exclude-newer sets the dependency cooldown and is on by default. Keeping it in the file makes the one useful mirror policy visible: fresh releases wait a week.

Install private and public packages from the same URL:

uv add --default-index http://HOST:8080/simple/ requests acme-widgets

With the proxy on, do not point clients at PyPI as an extra index. pypiron owns resolution and keeps private names private.

If the server only reaches the internet through a corporate forward proxy, set the standard HTTPS_PROXY/HTTP_PROXY/NO_PROXY environment variables — the proxy upstream, sync, and the advisory feed all honor them. If that proxy intercepts TLS with a private CA, add --upstream-ca-cert /path/to/corp-ca.pem so it validates without turning verification off. Details: Behind a forward proxy.

Mirror with sync

Use sync when the server should not talk to PyPI, or when CI should install from an approved list.

packages.txt:

requests>=2.32,<3
urllib3
six

pypiron.toml:

private-prefix = "acme"

[serve]
bind-addr = "0.0.0.0:8080"

[mirror]
include-packages-from = "packages.txt"
exclude-newer = "7 days"
include-format = ["wheel"]
exclude-platform-tag = ["win*", "macosx_*"]
exclude-python-below = "3.9"
exclude-prereleases = true

[sync]
from = "https://pypi.org"
to = "http://localhost:8080"
package-concurrency = 8

Run server and sync:

export PYPIRON_ADMIN_PASS="$ADMIN"
pypiron serve --config pypiron.toml
export PYPIRON_SYNC_ADMIN_PASS="$ADMIN"
pypiron sync --config pypiron.toml --dry-run
pypiron sync --config pypiron.toml

The same [mirror] table drives proxy and sync. Proxy can run open; sync needs include-packages or include-packages-from.

Object storage

Use object storage when you want more than one node, or when the package store should live outside the VM. Point --buckets at a bucket that already exists:

private-prefix = "acme"

[serve]
bind-addr = "0.0.0.0:8080"
buckets = ["s3://acme-pypiron@us-east-1"]
proxy-upstream = "https://pypi.org"

[mirror]
exclude-newer = "7 days"

AWS credentials come from the standard AWS chain: environment, web identity, instance role, or task role. GCS (gs://) and Azure (az://) work the same way with their own credentials — see Configuration. Several buckets across regions or clouds ride out an outage: Survive a region or cloud outage.

Run it

Docker Compose

services:
  pypiron:
    image: ghcr.io/blackthorn-interstellar/pypiron:latest
    command: serve --config /etc/pypiron/pypiron.toml
    ports:
      - "8080:8080"
    environment:
      PYPIRON_ADMIN_PASS: ${PYPIRON_ADMIN_PASS}
    volumes:
      - ./pypiron.toml:/etc/pypiron/pypiron.toml:ro

systemd

[Unit]
Description=pypiron
After=network-online.target
Wants=network-online.target

[Service]
Environment=PYPIRON_ADMIN_PASS=change-me
ExecStart=/usr/local/bin/pypiron serve --config /etc/pypiron/pypiron.toml
Restart=always
RestartSec=2

[Install]
WantedBy=multi-user.target

For local disk storage, mount a data volume and set data-dir in [serve]. For S3, run more containers with the same config behind a load balancer. Point the load balancer's health check at /ready (it turns 503 the moment a node starts draining, so the balancer stops it before shutdown).

Proxy or sync?

Use Pick Why
Normal private index plus cached public PyPI proxy No package list. Fetches on first install.
CI mirror with approved dependencies sync Pre-loads exactly what is in packages.txt.
Air-gapped serving node sync The server never needs egress.

Exact flags live in Configuration.