For the complete documentation index, see llms.txt.

Migrating a Python project to Chainguard Libraries

How to migrate an existing Python project to pull dependencies from Chainguard Libraries
  13 min read

Chainguard Libraries for Python provides a curated index of PyPI packages rebuilt from source and verified against Chainguard’s build provenance. Because Chainguard Libraries uses the standard pip simple-index protocol, switching an existing project requires only an index configuration change — no changes to your application code or dependency declarations.

This guide walks through migrating an existing Python project to Chainguard Libraries, covering the two most common setups:

  • Direct access: Your build tool connects directly to libraries.cgr.dev. This option is faster for initial evaluation and smaller-scale setups.
  • Repository manager: Your build tool connects to a repository manager (such as JFrog Artifactory or Sonatype Nexus), which proxies requests to Chainguard Libraries. This option is recommended for teams and organizations.

To follow along with a ready-made project instead of your own, use the Chainguard Libraries for Python demo repository. It provides example projects for pip, uv, and Poetry, each with a demo.sh script that configures access and installs sample packages.

For a reference of the configuration options for each supported build tool, check out Configure Python build tools.

Prepare for migration

A Python migration is more than changing an index URL; the application code usually stays the same, but dependency resolution can change because Python packages are distributed as different artifacts for different operating systems, Python versions, CPU architectures, and C libraries.

Plan for the following possible friction patterns:

  • The Chainguard catalog may not have every package, version, or wheel variant in an existing lockfile.
  • A dependency may be available for Linux but not for macOS, Windows, Alpine, or a particular Python/architecture combination.
  • A lockfile generated against PyPI contains hashes and artifact URLs that do not match Chainguard-built artifacts.
  • CI needs an authentication model that is safe for unattended builds and compatible with the package manager and repository topology.

Pre-migration readiness check

Before changing the index, record the dependency and environment matrix that the migration must support:

  • Python versions used in development, CI, and production
  • Operating systems and CPU architectures used by developers, CI runners, build images, and deployment jobs
  • The package manager and lockfile format used by each job: pip, uv, Poetry, or another supported tool
  • Direct dependencies and improtant transitive dependencies that include native code, GPU support, platform markers, or compiled extensions
  • Whether the project uses hash-pinned requirements or lockfiles
  • Where package caches exist: local machines, CI caches, repository managers, Docker layers, and base images

Run the existing build successfully before making changes. Keep the baseline dependency versions and record the commands used to install, test, and package the project. This gives your team a comparison point when a migration failure is caused by source selection, an artifact mismatch, or an unrelated dependency change.

Prerequisites

Before you begin, you need:

If you do not have an entitlement to Chainguard Libraries for Python yet, run the following command to create an entitlement and enable upstream fallback:

chainctl libraries entitlements create --ecosystems=PYTHON --policy=CHAINGUARD_AND_UPSTREAM

It can take up to 30 minutes for fallback policy changes to take effect.

Authentication prerequisites

For authentication, you need a pull token or the Python keyring provider.

If you plan to use a repository manager, or a non-interactive environment such as CI/CD, you will need a pull token. You must have the owner role or have the libraries.python.pull_token_creator permission to create one.

chainctl auth pull-token create --repository=python --name=my-python-token --ttl=720h

To export environment variables directly:

eval $(chainctl auth pull-token --output env --repository=python --name=my-python-token)

Learn more about creating and managing pull tokens in the Libraries access documentation.

Note: Do not commit credentials to version control. Store tokens as environment variables or CI secrets, not as literal values in pip.conf, .netrc, or any file that might be checked into a repository.

Step 1: Configure authentication and registry

How you configure the index depends on your environment. Follow the instructions that match your setup.

The https://libraries.cgr.dev/python/ endpoint is also the Chainguard Repository endpoint for Python. By default, it serves only Chainguard-built artifacts. When upstream fallback is enabled for your organization, the same endpoint can also serve requested versions from PyPI under Chainguard security controls.

Direct access

Configure authentication

First, configure authentication using a pull token or the Python keyring provider.

Setting pull token credentials in .netrc is a common approach for individual work stations. Setting authentication in pip.conf is preferred for CI/CD, where you need credentials isolated per project or per pipeline rather than a single shared home-directory file.

Note: .netrc only supports one set of credentials per hostname. Since all Chainguard Libraries are served from libraries.cgr.dev, configuring .netrc for Python will override credentials for any other ecosystem.

First, check for an existing registry entry in .netrc:

grep -A2 "libraries.cgr.dev" ~/.netrc

Remove or correct any stale entry before proceeding.

Then, configure your pull token credentials in .netrc:

cat >> ~/.netrc << EOF
machine libraries.cgr.dev
login ${CHAINGUARD_PYTHON_IDENTITY_ID}
password ${CHAINGUARD_PYTHON_TOKEN}
EOF
chmod 600 ~/.netrc

Point your build tool at Chainguard

Next, point your build tool at the Chainguard index.

Note that Chainguard publishes both standard and remediated Python indexes. Remediated versions use a +cgr.N local-version suffix, and Python package managers treat those as compatible higher-precedence replacements for the base version. In addition, CUDA-enabled Python libraries use separate CUDA-specific indexes such as https://libraries.cgr.dev/cu128/simple/, and they are not dependency-complete for NVIDIA toolkit components.

If you configured authentication via .netrc or the Python keyring provider, run the following commands to set the remediated repository first, then the simple repository:

pip config set global.index-url https://libraries.cgr.dev/python-remediated/simple/
pip config set global.extra-index-url https://libraries.cgr.dev/python/simple/

If you configured authentication via pull token credentials in pip.conf, this step is already done; the index-url you set in .pip/pip.conf already points at Chainguard with credentials embedded.

Repository manager

If your organization uses a repository manager, configure Chainguard Libraries as an upstream source in that proxy first. Follow the global configuration documentation for your repository manager.

Once configured, point your build tool at your repository manager URL instead of libraries.cgr.dev directly. In this setup, the credentials are your repository manager credentials — not a Chainguard pull token.

pip config set global.index-url https://<your-repo-manager-url>/repository/python-all/simple/

Example URLs by repository manager:

Repository managerURL pattern
JFrog Artifactoryhttps://example.jfrog.io/artifactory/api/pypi/python-all/simple/
Sonatype Nexushttps://repo.example.com:8443/repository/python-all/simple/
Google Artifact Registryhttps://<location>-python.pkg.dev/<project>/python-all/simple/
Cloudsmithhttps://dl.cloudsmith.io/.../<org>/python-all/python/simple/

For authentication details specific to your repository manager, refer to the global configuration documentation.

Step 2: Update your lockfile

Your existing lockfile or hash-pinned requirements.txt contains checksums generated against packages downloaded from PyPI or through your repository manager. Because Chainguard rebuilds packages from verified source as well as providing security controls for upstream artifacts, checksums differ even for identical version numbers. If your file uses --hash entries or --require-hashes, installation will fail with a hash mismatch after switching to Chainguard until these are updated.

You can update your lockfile in one of two ways. Update the checksums in place to keep your existing pinned versions, or regenerate the lockfile if you also want to refresh your dependency versions.

Use chainctl libraries update-hashes to rewrite only the integrity hashes in your existing lockfile or requirements file to match Chainguard’s artifacts, without re-resolving your dependency graph. Supported formats include requirements.txt, poetry.lock, uv.lock, pdm.lock, Pipfile.lock, and pylock.toml.

Run the following command to auto-detect and update the lockfile in the current project:

chainctl libraries update-hashes

Note: Running chainctl libraries update-hashes requires the libraries.python.pull permission or the Owner role.

Or specify the lockfile when running the command. For example:

chainctl libraries update-hashes requirements.txt

When using a repo manager, run a command similar to the following:

chainctl libraries update-hashes
  --registry-url https://repo.example.com/repository/python-all/
  --token "$REPO_TOKEN"

By default, Chainguard hashes are appended alongside your existing hashes rather than replacing them. If your installer fails on a dual-hash entry, use the --replace flag.

Step 3: Clear caches

Reinstalling after switching indexes can silently reuse a cached artifact from your previous index, with no error indicating this happened. To avoid this, clear caches.

pip cache purge

Also invalidate cached packages in any repository manager that previously proxied PyPI, and rebuild containers without cached layers if your Dockerfile installs dependencies during build:

docker build --no-cache .

Step 4: Reinstall dependencies

Reinstall dependencies and confirm that the lockfile reflects Chainguard as the source.

pip install --force-reinstall --no-cache-dir -r requirements.txt

Step 5: Verify your libraries

After reinstalling, you can use chainctl to verify which dependencies are built by Chainguard. When upstream fallback is enabled, libraries that aren’t built by Chainguard are subject to Chainguard’s security controls.

chainctl libraries verify --detailed ./.venv/

Note: Running chainctl libraries verify requires the libraries.python.pull permission or the Owner role.

A successful result shows what percentage of your project’s dependencies were built by Chainguard.

For full details on verification options and output, check out Verification: Analyze a Python wheel file.

Step 6: Commit and roll out

Commit the updated lockfile and any non-sensitive configuration changes. Apply the same index, cache, and hash-update steps to other developer workstations and build servers as you migrate them, including CI/CD platforms and any infrastructure that builds the application or installs dependencies.

For organization-wide rollout using a repository manager, refer to the global configuration documentation.

Packages not available in Chainguard Libraries

Chainguard Libraries covers a large and growing collection of PyPI packages, but not every package or version is available. If a package is missing, your install will fail with a 404 unless you have configured upstream fallback.

With upstream fallback enabled, packages not yet available from Chainguard are proxied from PyPI, subject to Chainguard’s security controls. Confirm your current policy with:

chainctl libraries entitlements list

For repository manager setups, Chainguard recommends using the configurable fallback rather than configuring a separate public registry fallback in your repository manager, to preserve Chainguard’s security controls.

Learn more about upstream fallback configurations in the Libraries overview.

Troubleshooting

“403 Forbidden” from the index despite a working keyring

Check for an old or incorrectly scoped .netrc entry for libraries.cgr.dev, which silently takes priority over keyring authentication. Run with full verbosity to confirm the credential source:

pip install -vvv <package> 2>&1 | grep -B2 -A2 "credentials\|40[13]"

A pinned dependency shows “not verified” after running chainctl libraries verify

Check your install output for a local wheel build (Building wheel for <package>), which indicates Chainguard has only a source distribution for that specific pinned version.

Handling coverage gaps

Coverage gaps can occur at more than one level. For example, a package name may not be present in the Chainguard catalog, the pinned version may not be available, a wheel may. be available for Linux but not for macOS or Windows, or a transitive dependency introduced by a version change may have different coverage from the direct dependency that caused the change.

Do not use a single successful install as proof that the migration is complete. Test each environment that installs the project.

A direct dependency can resolve successfully while one of its transitive dependencies, optional extras, or platform-specific wheels comes from upstream fallback. The reverse can also happen: the direct package may use fallback while its transitive dependencies are available as Chainguard-built artifacts. Evaluate the resolved artifact set, not only the names in pyproject.toml or requirements.in.

Choose how to handle coverage gaps:

  • Confirm whether the failure is authentication or coverage. A 401/403 is an empty version list, and a genuine 404 has different causes.
  • Check the exact package version and wheel tags required by the failing environment.
  • If the artifact is not available from Chainguard, use the configured protected upstream fallback when policy allows it.
  • If the project cannot use fallback, choose a compatible version or move the install to a supported Linux environment only when that is an acceptable product decision.
  • Do not silently replace a package with a different version during migration; treat it as a separate depdendency change and test it separately.

Next steps

  • To apply this configuration across your whole organization using a repository manager, refer to the global configuration documentation.
  • To verify downloaded packages were built by Chainguard, refer to the verification documentation.
  • For full per-tool configuration reference, refer to the build configuration documentation.
  • To keep pinned versions stable when Chainguard publishes new builds, refer to the build pinning documentation.

Last updated: 2026-10-13 14:42