# How to use private package indexes with uv


Most organizations host internal Python packages on private registries. [uv](https://pydevtools.com/handbook/reference/uv.md) 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`:

```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:

```toml
[[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:

```toml
[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:

```bash
export UV_INDEX_PRIVATE_USERNAME="token"
export UV_INDEX_PRIVATE_PASSWORD="secret-value"
```
```powershell
$env:UV_INDEX_PRIVATE_USERNAME = "token"
$env:UV_INDEX_PRIVATE_PASSWORD = "secret-value"
```
### Credentials in URLs

Credentials can be embedded directly in the index URL:

```toml
[[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):

```text
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:

```bash
uv lock --keyring-provider subprocess
```

Or set it permanently:

```bash
export UV_KEYRING_PROVIDER=subprocess
```
```powershell
$env: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:

```bash
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
```
```powershell
$env:CODEARTIFACT_TOKEN = (aws codeartifact get-authorization-token `
    --domain my-domain `
    --domain-owner 123456789012 `
    --query authorizationToken `
    --output text)

$env:UV_INDEX_PRIVATE_USERNAME = "aws"
$env:UV_INDEX_PRIVATE_PASSWORD = $env:CODEARTIFACT_TOKEN
```
The corresponding index configuration:

```toml
[[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:

```bash
export UV_INDEX_GOOGLE_USERNAME=oauth2accesstoken
export UV_INDEX_GOOGLE_PASSWORD=$(gcloud auth print-access-token)
```
```powershell
$env:UV_INDEX_GOOGLE_USERNAME = "oauth2accesstoken"
$env:UV_INDEX_GOOGLE_PASSWORD = (gcloud auth print-access-token)
```
```toml
[[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:

```bash
export UV_INDEX_ARTIFACTORY_USERNAME="your-username"
export UV_INDEX_ARTIFACTORY_PASSWORD="your-api-key"
```
```powershell
$env:UV_INDEX_ARTIFACTORY_USERNAME = "your-username"
$env:UV_INDEX_ARTIFACTORY_PASSWORD = "your-api-key"
```
```toml
[[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](https://pydevtools.com/handbook/explanation/what-is-pep-503.md) Simple API. The URL format depends on the Nexus version and repository name:

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

Authenticate with a Nexus local user account:

```bash
export UV_INDEX_NEXUS_USERNAME="your-username"
export UV_INDEX_NEXUS_PASSWORD="your-password"
```
```powershell
$env:UV_INDEX_NEXUS_USERNAME = "your-username"
$env: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:

```toml
[[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:

```bash
export UV_INDEX_AZURE_USERNAME="anything"
export UV_INDEX_AZURE_PASSWORD="your-pat-token"
```
```powershell
$env:UV_INDEX_AZURE_USERNAME = "anything"
$env:UV_INDEX_AZURE_PASSWORD = "your-pat-token"
```
```toml
[[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:

```bash
export UV_SYSTEM_CERTS=true
```
```powershell
$env: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):

```toml
system-certs = true
```

Point to a specific certificate file or directory:

```bash
export SSL_CERT_FILE=/etc/ssl/certs/corporate-ca-bundle.crt
```
```powershell
$env:SSL_CERT_FILE = "C:\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:

```yaml
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:

```toml
[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](https://pydevtools.com/handbook/explanation/uv-complete-guide.md) covers what uv does, how fast it is, the core workflows, and recent releases.
