Install Python packages through Zaryn with pip, uv or Poetry, publish your own, install Python itself, and keep private packages private. One index for your projects and pypi.org, safe from look-alike packages. Every step explained.
Python's installers (**pip**, **uv**, **Poetry**) download packages from an *index*. Zaryn gives you one index for everything:
- **Your projects** published on Zaryn, public or private.
- **Everything on pypi.org**, through Zaryn's mirror: every file is checked against the sha256 pypi.org lists before Zaryn keeps it, and pip checks it again.
- **Safe from look-alikes.** A name that exists on Zaryn always comes from Zaryn, so nobody can publish a same-named package on pypi.org and sneak it into your installs (this is called *dependency confusion*). And new names on Zaryn can't be ones pypi.org already has, so nobody can do it the other way round either.
The index's address:
```text
https://api.zaryn.xyz/v1/packages/pypi/all/simple/
```
## Install packages through Zaryn
### pip
Once per computer:
```bash
pip config set global.index-url https://api.zaryn.xyz/v1/packages/pypi/all/simple/
```
That's all: `pip install requests` now goes through Zaryn. Or for one command only:
```bash
pip install --index-url https://api.zaryn.xyz/v1/packages/pypi/all/simple/ requests
```
### uv
For every project on this computer, set it in your shell start file (`~/.bashrc` or similar):
```bash
export UV_DEFAULT_INDEX=https://api.zaryn.xyz/v1/packages/pypi/all/simple/
```
Or for one project, in its `pyproject.toml`:
```toml
[[tool.uv.index]]
name = "zaryn"
url = "https://api.zaryn.xyz/v1/packages/pypi/all/simple/"
default = true
```
`uv add`, `uv sync` and `uv pip install` then use Zaryn.
### Poetry
```bash
poetry source add --priority=primary zaryn https://api.zaryn.xyz/v1/packages/pypi/all/simple/
```
### All of them at once
With the `zev` command installed, `zev mirror on pip uv` writes the pip and uv settings for you, and `zev mirror off pip uv` removes them. See [Mirrors](/docs/en/package-mirrors#all-at-once-zev-mirror).
## Install Python itself (uv)
uv can download Python too (`uv python install 3.13`, or automatically when a project needs it). Those downloads come from GitHub; to get them through Zaryn:
```bash
export UV_PYTHON_INSTALL_MIRROR=https://api.zaryn.xyz/v1/packages/python/-/standalone
uv python install 3.13
```
Each build is checked against the release's SHA256SUMS before Zaryn keeps it, and uv checks it against the checksum it has built in.
## Publish your own package
### Build it
From the folder with your `pyproject.toml`:
```bash
python -m pip install build twine
python -m build
```
This puts a wheel (`.whl`) and a source archive (`.tar.gz`) into `dist/`.
### Upload it
Create a token on [Packages](/packages) (**Create a publish token**). Then, with twine:
```bash
twine upload --repository-url https://api.zaryn.xyz/v1/packages/pypi/ \
-u __token__ -p YOUR_TOKEN dist/*
```
- `-u __token__` is literal: it tells Zaryn the password is a token.
- To avoid typing it each time, put this in `~/.pypirc` and run `twine upload -r zaryn dist/*`:
```ini
[distutils]
index-servers = zaryn
[zaryn]
repository = https://api.zaryn.xyz/v1/packages/pypi/
username = __token__
password = YOUR_TOKEN
```
With uv or Poetry instead:
```bash
uv publish --publish-url https://api.zaryn.xyz/v1/packages/pypi/ --username __token__ --password YOUR_TOKEN
poetry config repositories.zaryn https://api.zaryn.xyz/v1/packages/pypi/
poetry publish -r zaryn --username __token__ --password YOUR_TOKEN
```
A version that's already uploaded can't be replaced: raise the version in `pyproject.toml` for a fix.
### Choosing a name
A **new** name must not exist on pypi.org; Zaryn checks when you first upload. If it's taken there, pick another, for example with your team's prefix (`acme-utils`). This is what keeps the single index safe for everyone.
### Install it
With the index set up as above, it's just `pip install your-package` or `uv add your-package`. Its dependencies from pypi.org come through the same index.
## Install straight from a Zaryn repository
Any commit of a repository with a `pyproject.toml` (or `setup.py`) can be installed without publishing it first: open the repository, then the **install** tab next to the clone commands, and copy the line. It looks like this:
```bash
pip install https://zaryn.xyz/api/v1/repos/<owner>/<repo>/branches/main/archive/<commit>.tar.gz
```
The address names a commit, so it always installs exactly the same code. In `pyproject.toml` the same works as a dependency: `"mytool @ https://zaryn.xyz/api/v1/repos/…/archive/<commit>.tar.gz"`. The repository must be public (or give pip your token in the address).
## Private packages (Premium)
Make a package private on its page (**Make private**). Only you, or members of the organization that owns it, can see or install it. Installers then need your token, written into the index address:
```bash
pip install --index-url https://__token__:$ZARYN_TOKEN@api.zaryn.xyz/v1/packages/pypi/all/simple/ your-package
```
For uv, set `UV_INDEX_ZARYN_USERNAME=__token__` and `UV_INDEX_ZARYN_PASSWORD=$ZARYN_TOKEN` (with the `zaryn` index from `pyproject.toml` above).
## When something goes wrong
| You see | What it means | Fix |
|---|---|---|
| `403` "… is a project on pypi.org" when uploading | new names can't be ones pypi.org has | choose another name |
| `403` "this token can't publish packages" | the token lacks `package:write` | create one on [Packages](/packages) |
| `403` "this package name belongs to someone else" | another account uploaded that name first | choose another name |
| `409` | that file was already uploaded | raise the version |
| `503` "couldn't check that this name is free on pypi.org" | pypi.org didn't answer when you uploaded a new name | try again in a minute |
| pip: "No matching distribution" | wrong index address, or a private package without the token | check the address ends in `/all/simple/` |
| pip: "not a trusted or secure host" | the address starts with `http://` | use the `https://` address |
## How it works
Every index Zaryn serves speaks both formats installers use: the classic HTML page (PEP 503) and JSON (PEP 691), whichever the installer asks for. It also gives each wheel's metadata as a separate small file (PEP 658), so pip and uv work out which versions fit without downloading whole wheels; this makes resolving noticeably faster. Files are stored by their SHA-256, which the index lists, so the installer checks every download. Names are normalized the way PyPI does it: `My_Package` and `my-package` are the same name.
The addresses, if you need them separately: `/v1/packages/pypi/simple/` is only your projects on Zaryn, `/v1/packages/pypi/mirror/simple/` is only pypi.org, and `/v1/packages/pypi/all/simple/` is both.
Python's installers (pip, uv, Poetry) download packages from an index. Zaryn gives you one index for everything:
Your projects published on Zaryn, public or private.
Everything on pypi.org, through Zaryn's mirror: every file is checked against the sha256 pypi.org lists before Zaryn keeps it, and pip checks it again.
Safe from look-alikes. A name that exists on Zaryn always comes from Zaryn, so nobody can publish a same-named package on pypi.org and sneak it into your installs (this is called dependency confusion). And new names on Zaryn can't be ones pypi.org already has, so nobody can do it the other way round either.
uv can download Python too (uv python install 3.13, or automatically when a project needs it). Those downloads come from GitHub; to get them through Zaryn:
A new name must not exist on pypi.org; Zaryn checks when you first upload. If it's taken there, pick another, for example with your team's prefix (acme-utils). This is what keeps the single index safe for everyone.
Any commit of a repository with a pyproject.toml (or setup.py) can be installed without publishing it first: open the repository, then the install tab next to the clone commands, and copy the line. It looks like this:
The address names a commit, so it always installs exactly the same code. In pyproject.toml the same works as a dependency: "mytool @ https://zaryn.xyz/api/v1/repos/…/archive/<commit>.tar.gz". The repository must be public (or give pip your token in the address).
Make a package private on its page (Make private). Only you, or members of the organization that owns it, can see or install it. Installers then need your token, written into the index address:
Every index Zaryn serves speaks both formats installers use: the classic HTML page (PEP 503) and JSON (PEP 691), whichever the installer asks for. It also gives each wheel's metadata as a separate small file (PEP 658), so pip and uv work out which versions fit without downloading whole wheels; this makes resolving noticeably faster. Files are stored by their SHA-256, which the index lists, so the installer checks every download. Names are normalized the way PyPI does it: My_Package and my-package are the same name.
The addresses, if you need them separately: /v1/packages/pypi/simple/ is only your projects on Zaryn, /v1/packages/pypi/mirror/simple/ is only pypi.org, and /v1/packages/pypi/all/simple/ is both.