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 = trueWhen 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:[email protected]/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-valueThis 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 subprocessOr set it permanently:
export UV_KEYRING_PROVIDER=subprocessThis 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_TOKENThe 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. Two options solve this.
Use the platform certificate store:
export UV_SYSTEM_CERTS=trueOr pass --system-certs to any uv command. This tells uv to use the operating system’s certificate store, which typically includes corporate CA certificates installed by IT.
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 = truePoint to a specific certificate file or directory:
export SSL_CERT_FILE=/etc/ssl/certs/corporate-ca-bundle.crtSSL_CERT_FILE points to a single PEM-encoded CA bundle. SSL_CERT_DIR points to a directory of individual PEM certificate files. Both are respected by uv and most other Python tools.
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.
Route traffic through an HTTP proxy
Corporate networks that route outbound traffic through an HTTP proxy require standard proxy environment variables:
export HTTPS_PROXY=http://proxy.company.com:8080
export HTTP_PROXY=http://proxy.company.com:8080Use NO_PROXY to bypass the proxy for specific hosts, such as an internal registry that is reachable without it:
export NO_PROXY=nexus.company.com,internal.example.comALL_PROXY sets a proxy for both HTTP and HTTPS in a single variable.
If the proxy itself requires authentication, include credentials in the URL:
export HTTPS_PROXY=http://user:[email protected]:8080Tip
When combining a proxy with a TLS-intercepting firewall, set both the proxy variables and UV_SYSTEM_CERTS=true. The proxy handles routing while native TLS handles the corporate CA certificate.
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 pytestThe 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.