uv Repositories
uv installs from a CloudRepo Python repository and publishes to it. CloudRepo serves the same simple index and the same upload API that pip and twine use, so uv needs the repository’s address and a credential.
Before you start
Section titled “Before you start”- A Python repository. If you have none, see Creating a Repository.
- A repository token that reaches it, with Read + write if you will publish. See Repository Tokens: Create One and Authenticate. The username is the email address of the account that created the token, and the password is the token.
- uv, installed from uv’s own instructions.
Publish and install
Section titled “Publish and install”1. Put the credential in ~/.netrc. uv reads it for the host on the machine line. Keep the
file readable by you alone (chmod 600 ~/.netrc):
machine your-org.mycloudrepo.iologin you@example.compassword YOUR_REPOSITORY_TOKEN2. Build and publish. You need a project that builds a wheel or a source distribution (see uv’s
building and publishing guide). Give uv publish its
credential in two environment variables rather than as options, which would put the token on the command
line. Set CLOUDREPO_USERNAME and CLOUDREPO_TOKEN from your secret store, and give uv the repository URL
without /simple/:
uv buildUV_PUBLISH_USERNAME="$CLOUDREPO_USERNAME" UV_PUBLISH_PASSWORD="$CLOUDREPO_TOKEN" \ uv publish --publish-url https://your-org.mycloudrepo.io/repositories/your-repo dist/*Expected: uv prints Uploading and the file name for each file in dist/, and exits without an
error. The files are then in the repository, and the admin portal lists
them.
3. Install. uv pip install takes the index URL with /simple/. With --index-url CloudRepo is
the only index for that command, which is right for a package that has no public dependencies. uv
finds the credential in ~/.netrc, so the URL carries none:
uv venvuv pip install --index-url https://your-org.mycloudrepo.io/repositories/your-repo/simple/ docs-uv==1.0.0Expected: uv prints Installed 1 package and + docs-uv==1.0.0.
Pin private packages in a project
Section titled “Pin private packages in a project”In a project, name CloudRepo as an index that serves only the packages you pin to it. An index with
explicit = true is asked for nothing else, so every other package still comes from PyPI.
Pin every private package before you run uv lock or uv sync, including each private package that your
private packages need. uv applies a [tool.uv.sources] entry only to a package that dependencies or a
dependency group lists. It ignores an entry for any other name, and it prints no warning. It looks that name
up on PyPI instead, where anyone can publish a package under your private package’s name, and uv locks and
installs it. List each private package in dependencies and in [tool.uv.sources].
When a private package starts to need another private package, add that one to dependencies and to
[tool.uv.sources] too before you run uv lock --upgrade. Otherwise uv takes it from PyPI.
The example is a project that depends on the package you just published:
[project]name = "my-service"version = "0.1.0"requires-python = ">=3.9"dependencies = ["docs-uv"]
[[tool.uv.index]]name = "cloudrepo"url = "https://your-org.mycloudrepo.io/repositories/your-repo/simple/"explicit = true
[tool.uv.sources]docs-uv = { index = "cloudrepo" }Lock and install. uv finds the credential in the same ~/.netrc:
uv lockuv sync --lockedExpected: uv lock writes uv.lock, which records CloudRepo as the source of docs-uv. uv sync --locked
checks the environment against that lock file. If docs-uv is already in .venv, as after the
uv pip install earlier on this page, it prints Checked 1 package. In an environment that does not hold
it yet, it installs the package and prints + docs-uv==1.0.0.
Three more things to get right:
- Do not make PyPI an extra index beside CloudRepo. uv asks an
--extra-index-urlindex before the default index, so with CloudRepo as--index-urland PyPI as the extra index, a package someone publishes on PyPI under your private package’s name is installed instead of yours. - Where a file is awkward, such as CI, set
UV_INDEX_CLOUDREPO_USERNAMEandUV_INDEX_CLOUDREPO_PASSWORDfrom your secret store. uv readsUV_INDEX_<NAME>_USERNAMEandUV_INDEX_<NAME>_PASSWORDfor the index called<NAME>, written in upper case.UV_INDEX_USERNAMEandUV_INDEX_PASSWORDare not uv settings: uv sends no credential for them and the repository answers401. - Publish with
--publish-url, as above.uv publish --index cloudrepoalso reads the index, and CloudRepo’s index lists no file hashes, so uv stops withHash is missing in indexfor a file that is already there.
When uv answers 401, 403, 404 or 409
Section titled “When uv answers 401, 403, 404 or 409”- 401. uv prints
could not be queried due to a lack of valid authentication credentials (401 Unauthorized), and on a publishServer returned status code 401 Unauthorized. The credential was refused or never sent. The username must be the email address of the account that created the token.__token__is refused: that is the convention on pypi.org, and CloudRepo does not use it. The password must be the token itself, and a token that is expired or revoked fails the same way: the Repository Tokens page in the admin portal shows its status. Check that themachineline of~/.netrcis exactly the host in the URL, and that the variable names are the ones above. - 403. uv prints
returned a 403 Forbidden error, and on a publishServer returned status code 403 Forbidden. The token does not reach this repository, or it is Read only and you published. A token reaches only the repositories ticked when it was created. - 404. uv prints
was not found in the package registry, naming the package. The repository does not hold that name, or the URL is wrong: check the organization and repository names in it. A proxy repository that the token does not reach also answers404, not403. - 409, on publish. The repository has Overwrite Protection on, and a file at that path already exists.
uv prints
Server returned status code 409 Conflictand, afterServer says:, the explanation CloudRepo sends with it. Upload a new version, or turn Overwrite Protection off for that repository: see Python Repositories.
More: Repository tokens, for every client’s credential; Python Repositories, for pip and twine and the Python repository settings; Install Python packages from CloudRepo, for installing with pip.