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: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-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 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=trueOr 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.crtThe 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 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.
Get Python tooling updates
Subscribe to the newsletter