Skip to content

How to use private package indexes with uv

uv

Most organizations host internal Python packages on private registries. uv supports private indexes through pyproject.toml configuration, with several authentication methods that work across local development and CI.

Configure a private index

Add a [[tool.uv.index]] entry to pyproject.toml:

[[tool.uv.index]]
name = "private"
url = "https://private.example.com/simple"

The name field is required and must be unique across all configured indexes.

To replace PyPI entirely with a custom index, set the default flag:

[[tool.uv.index]]
name = "company-pypi"
url = "https://company.example.com/simple"
default = true

Packages not found on the default index will fail to resolve, unless additional non-default indexes are also configured.

Pin packages to specific indexes

Use tool.uv.sources to force a package to resolve from a particular index:

[project]
dependencies = ["my-internal-lib", "requests"]

[[tool.uv.index]]
name = "internal"
url = "https://internal.example.com/simple"

[tool.uv.sources]
my-internal-lib = { index = "internal" }

Without this pinning, uv searches all configured indexes for every package. Pinning prevents a malicious package on PyPI from shadowing an internal package name (a dependency confusion attack).

Authenticate with a private index

Environment variables

The most portable method. Set UV_INDEX_<NAME>_USERNAME and UV_INDEX_<NAME>_PASSWORD, where <NAME> is the uppercase version of the index name:

export UV_INDEX_PRIVATE_USERNAME="token"
export UV_INDEX_PRIVATE_PASSWORD="secret-value"

Credentials in URLs

Credentials can be embedded directly in the index URL:

[[tool.uv.index]]
name = "private"
url = "https://username:password@private.example.com/simple"

Warning

Avoid this approach for anything other than local testing. Credentials in pyproject.toml end up in version control, and credentials in uv.lock are visible to anyone with repository access.

Netrc files

uv reads credentials from ~/.netrc (or %USERPROFILE%\_netrc on Windows):

machine private.example.com
    login token
    password secret-value

Keyring integration

For systems that store credentials in the OS keyring (macOS Keychain, Windows Credential Manager, or Linux secret services), enable keyring support:

uv lock --keyring-provider subprocess

Or set it permanently:

export UV_KEYRING_PROVIDER=subprocess

This requires the keyring package to be installed and accessible on PATH.

Set up AWS CodeArtifact

AWS CodeArtifact provides a temporary auth token that expires after 12 hours by default:

export CODEARTIFACT_TOKEN=$(aws codeartifact get-authorization-token \
    --domain my-domain \
    --domain-owner 123456789012 \
    --query authorizationToken \
    --output text)

export UV_INDEX_PRIVATE_USERNAME=aws
export UV_INDEX_PRIVATE_PASSWORD=$CODEARTIFACT_TOKEN

The corresponding index configuration:

[[tool.uv.index]]
name = "private"
url = "https://my-domain-123456789012.d.codeartifact.us-east-1.amazonaws.com/pypi/my-repo/simple/"

Set up Google Artifact Registry

Use a service account key or application default credentials:

export UV_INDEX_GOOGLE_USERNAME=oauth2accesstoken
export UV_INDEX_GOOGLE_PASSWORD=$(gcloud auth print-access-token)
[[tool.uv.index]]
name = "google"
url = "https://us-central1-python.pkg.dev/my-project/my-repo/simple/"

For CI environments with a service account JSON key, the keyrings.google-artifactregistry-auth package combined with --keyring-provider subprocess automates token refresh.

Set up JFrog Artifactory

Generate an API key from the Artifactory UI under User Profile > API Key, then configure:

export UV_INDEX_ARTIFACTORY_USERNAME="your-username"
export UV_INDEX_ARTIFACTORY_PASSWORD="your-api-key"
[[tool.uv.index]]
name = "artifactory"
url = "https://company.jfrog.io/artifactory/api/pypi/python-local/simple"

Artifactory also supports identity tokens and access tokens as alternatives to API keys.

Set up Sonatype Nexus Repository

Nexus Repository Manager exposes a PyPI proxy or hosted repository through its PEP 503 Simple API. The URL format depends on the Nexus version and repository name:

[[tool.uv.index]]
name = "nexus"
url = "https://nexus.company.com/repository/pypi-proxy/simple"

Authenticate with a Nexus local user account:

export UV_INDEX_NEXUS_USERNAME="your-username"
export UV_INDEX_NEXUS_PASSWORD="your-password"

Nexus Repository Pro also supports user tokens, which avoid exposing account passwords. When using a user token, set the token name as the username and the token passcode as the password.

Nexus supports both proxy repositories (caching a remote index like PyPI) and hosted repositories (storing internal packages). To use both, configure two indexes and pin internal packages to the hosted repository:

[[tool.uv.index]]
name = "nexus-proxy"
url = "https://nexus.company.com/repository/pypi-proxy/simple"
default = true

[[tool.uv.index]]
name = "nexus-hosted"
url = "https://nexus.company.com/repository/pypi-hosted/simple"

[tool.uv.sources]
my-internal-lib = { index = "nexus-hosted" }

Setting the proxy repository as default = true replaces PyPI, so all public packages route through the corporate proxy cache.

Set up Azure Artifacts

Create a Personal Access Token (PAT) with Packaging > Read scope:

export UV_INDEX_AZURE_USERNAME="anything"
export UV_INDEX_AZURE_PASSWORD="your-pat-token"
[[tool.uv.index]]
name = "azure"
url = "https://pkgs.dev.azure.com/my-org/_packaging/my-feed/pypi/simple/"

Azure Artifacts accepts any string as the username when using a PAT.

Handle TLS certificates for corporate proxies

Corporate networks often use TLS-intercepting proxies with custom root certificates. By default, uv uses its own bundled certificate store, which does not include corporate CA certificates.

Use the platform certificate store:

export UV_SYSTEM_CERTS=true

Or pass --system-certs to any uv command.

To enable this permanently, add it to a project-level uv.toml or a user-level uv.toml at ~/.config/uv/uv.toml (Linux/macOS) or %APPDATA%\uv\uv.toml (Windows):

system-certs = true

Point to a specific certificate file or directory:

export SSL_CERT_FILE=/etc/ssl/certs/corporate-ca-bundle.crt

SSL_CERT_FILE points to a single PEM-encoded CA bundle. SSL_CERT_DIR points to a directory of individual PEM certificate files.

For registries that require mutual TLS (client certificate authentication), set SSL_CLIENT_CERT to a PEM file containing both the client certificate and private key.

Use private indexes in CI

In GitHub Actions, store credentials as repository secrets and expose them as environment variables:

name: CI
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    env:
      UV_INDEX_PRIVATE_USERNAME: ${{ secrets.PRIVATE_INDEX_USERNAME }}
      UV_INDEX_PRIVATE_PASSWORD: ${{ secrets.PRIVATE_INDEX_PASSWORD }}
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v7
      - run: uv sync --frozen
      - run: uv run pytest

Choose an index search strategy

When multiple indexes are configured, the index-strategy setting controls how uv searches them:

[tool.uv]
index-strategy = "first-index"

first-index (the default): uv searches indexes in order and uses the first one that contains a given package.

unsafe-first-match: uv searches indexes in order and picks the first version that satisfies the requirement, even if a higher version exists on a later index.

unsafe-best-match: uv searches all indexes and picks the highest compatible version across all of them. Both unsafe- strategies open the door to dependency confusion, where a version on a public index could override an internal package.

Use first-index unless there is a specific reason to compare versions across indexes. When using first-index, list private indexes before PyPI in pyproject.toml so that internal packages are found first.

Learn more

  • uv: A Complete Guide covers what uv does, how fast it is, the core workflows, and recent releases.
Last updated on