Skip to content

How to use private package indexes with 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"

This tells uv to search the private index in addition to PyPI when resolving packages. 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

When default = true, uv uses this index instead of PyPI. 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"

These environment variables correspond to the name = "private" field in the [[tool.uv.index]] entry.

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

This works well for developer machines where multiple tools (curl, pip, uv) need the same credentials.

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. uv calls keyring get <url> <username> as a subprocess to retrieve credentials.

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 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. Two options solve this.

Use the platform certificate store:

export UV_NATIVE_TLS=true

Or pass --native-tls to any uv command. This tells uv to use the operating system’s certificate store, which typically includes corporate CA certificates installed by IT.

Point to a specific certificate file:

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

The SSL_CERT_FILE environment variable is respected by uv and most other Python tools.

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@v6
      - run: uv sync --frozen
      - run: uv run pytest

The astral-sh/setup-uv action installs uv and configures caching. Environment variables set at the job level apply to all steps, keeping credentials available throughout the pipeline without repeating them.

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. Once a package is found on an index, other indexes are not consulted for that 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.

Get Python tooling updates

Subscribe to the newsletter
Last updated on

Please submit corrections and feedback...