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. This page covers connecting to a registry that already exists; to run one, see how to host your own package index.
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 = truePackages 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
Use uv auth login to save credentials for local development. Use environment variables for CI jobs or temporary tokens.
Save credentials with uv auth
Replace private.example.com with the host in the configured index URL and your-username with the registry username. For token-based services, use the username required by the provider, such as aws for CodeArtifact:
uv auth login private.example.com --username your-usernameEnter the password or access token at the hidden prompt. Expect Stored credentials for your-username@https://private.example.com/. Login saves credentials without checking them against the server; run the project’s next uv sync to verify access to a private dependency.
The default credential store is a plaintext file outside the project. To locate it:
uv auth dirExpect the path to uv’s credentials directory, which varies by operating system. Keep that directory private; do not commit or share it.
To retrieve the saved password or token for the same username:
uv auth token private.example.com --username your-usernameThis prints the secret to standard output. Run it only when the value is needed, and keep its output out of shared terminals and CI logs.
To remove the saved credentials:
uv auth logout private.example.com --username your-usernameExpect Removed credentials for your-username@https://private.example.com/. Logout removes the local entry; revoke the token at the registry if it must stop working everywhere. If an existing .netrc entry has stale credentials, update or remove that entry because uv checks it before the uv credential store.
Set environment variables
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
Keep credentials out of project files. A password written into pyproject.toml is exposed if that file is committed. uv add strips index credentials when writing project and lock files, but credentials in direct package URLs are persisted.
Netrc files
uv reads credentials from ~/.netrc (or %USERPROFILE%\_netrc on Windows):
machine private.example.com
login token
password secret-valueKeyring 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.
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.
Use the platform certificate store:
export UV_SYSTEM_CERTS=trueOr 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 = 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.
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@v10.1.0
- run: uv sync --frozen
- run: uv run pytestChoose 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.