Remote Publishing
Advanced
Package Manager supports adding packages to local Python or R repositories remotely. This feature allows easier integration with separate CI/CD package build pipelines or adding packages directly from a development environment.
Remote Authentication Methods
Package Manager provides multiple ways to authenticate for remote operations:
- Single Sign-On (SSO) Authentication: Use your organization’s OpenID Connect provider for interactive sessions
- Identity Federation: Use external OpenID Connect tokens with automatic token exchange for CI/CD and third-party integrations
- API Token Authentication: Use long-lived API tokens for CI/CD pipelines and automated systems
Single Sign-On (SSO) Authentication
If your Package Manager server is configured with OpenID Connect authentication, you can use SSO to authenticate interactively. This method is ideal for individual users who want to publish packages from their development environment.
SSO authentication requires that Package Manager is configured with an OpenID Connect provider. For more information on configuring OpenID Connect, see the OpenID Connect Authentication documentation.
Identity Federation
If your Package Manager server is configured with identity federation, you can use external OpenID Connect tokens (such as GitHub Actions OIDC tokens) that are automatically exchanged for Package Manager tokens. This method is ideal for CI/CD pipelines and automated systems that already have access to OpenID Connect tokens from external providers.
Identity federation requires that Package Manager is configured with one or more IdentityFederation
providers. For more information on configuring identity federation, see the OpenID Connect Identity Federation documentation.
API Token Authentication
For automated systems and CI/CD pipelines, you can use API tokens for authentication. An API token needs to be generated to allow a user to access Package Manager remotely. For example, to create a token that supports publishing packages to a remote server, run this command on the server:
Terminal
$ rspm create token --description="Local Python token" --sources=local-python-src --expires=30d --scope=sources:write --user="python-publisher"
Generated an access token. Be sure to record this token immediately since you will not be able to retrieve it later.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJwYWNrYWdlbWFuYWdlciIsImV4cCI6MTY1ODU4MjA3OCwianRpIjoiYmM5ZTg1NGYtNGNlNy00Zjc4LTlhMmMtZDliYzRlYTQ0NGVkIiwiaWF0IjoxNjU1OTkwMDc4LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQyNDIvIiwic2NvcGVzIjp7InNvdXJjZXMiOiIzYTI4NjFhYi0xNWYwLTRjM2MtODZlMy0xNjNkMTY0ZDE0ZDYifX0.BWJXLworo44Nvfrh5a2hm_NIqgUoXTLjQlxyy7uaSWk
Uploading Packages Remotely with the CLI
Method 1: Using rspm login
(Recommended for Interactive Use)
For interactive sessions, you can use the rspm login
commands to authenticate:
Terminal
# Set the server address
$ export PACKAGEMANAGER_ADDRESS=https://packagemanager.example.com
# Download the CLI
$ curl -fLOJ "${PACKAGEMANAGER_ADDRESS}/__api__/download" && chmod +x rspm
# Log in using SSO (requires OpenID Connect configuration)
$ ./rspm login sso
# Upload packages
$ ./rspm add --source=local-python-src --path=/path/to/packages
The rspm login sso
command will start a device authorization flow and authenticate you through your organization’s OpenID Connect provider. For more details on device authorization, see the Device Authorization section.
Terminal
# Set the server address
$ export PACKAGEMANAGER_ADDRESS=https://packagemanager.example.com
# Download the CLI
$ curl -fLOJ "${PACKAGEMANAGER_ADDRESS}/__api__/download" && chmod +x rspm
# Log in using an API token (created using the steps above)
$ ./rspm login
Enter your token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# Upload packages
$ ./rspm add --source=local-python-src --path=/path/to/packages
Method 2: Using Identity Federation (For CI/CD with External OIDC Tokens)
For CI/CD systems and automation environments that already have access to OpenID Connect tokens from external providers (such as GitHub Actions, GitLab CI, or other identity providers), you can use the PACKAGEMANAGER_IDENTITY_TOKEN_FILE
environment variable to automatically perform token exchange:
Terminal
# Set the server address
$ export PACKAGEMANAGER_ADDRESS=https://packagemanager.example.com
# Set the path to a file containing your external OIDC token
# (e.g., GitHub Actions token, GitLab CI token, etc.)
$ export PACKAGEMANAGER_IDENTITY_TOKEN_FILE=/path/to/oidc-token-file
# Download the CLI
$ curl -fLOJ "${PACKAGEMANAGER_ADDRESS}/__api__/download" && chmod +x rspm
# The CLI will automatically perform token exchange using the external token
$ ./rspm add --source=local-python-src --path=/path/to/packages
When PACKAGEMANAGER_IDENTITY_TOKEN_FILE
is set and Package Manager is configured with matching identity federation providers, the rspm
CLI will:
- Read the external OpenID Connect token from the specified file
- Automatically perform an RFC 8693 token exchange to obtain a Package Manager token
- Use the exchanged token for API authentication
- Execute the requested CLI command
This method is particularly useful for:
- GitHub Actions workflows that obtain OIDC tokens via
ACTIONS_ID_TOKEN_REQUEST_TOKEN
- GitLab CI pipelines with
CI_JOB_JWT_V2
tokens - Azure DevOps with federated credentials
- Other CI/CD systems that provide OpenID Connect tokens
This method requires that Package Manager is configured with appropriate IdentityFederation
providers that match your external token’s issuer, audience, and subject claims. For more details and configuration examples, see the OpenID Connect Identity Federation guide.
Method 3: Using Environment Variables (For CI/CD and Automation)
For automated systems and CI/CD pipelines, you can set environment variables directly:
Terminal
$ export PACKAGEMANAGER_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJwYWNrYWdlbWFuYWdlciIsImV4cCI6MTY1ODU4MjA3OCwianRpIjoiYmM5ZTg1NGYtNGNlNy00Zjc4LTlhMmMtZDliYzRlYTQ0NGVkIiwiaWF0IjoxNjU1OTkwMDc4LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQyNDIvIiwic2NvcGVzIjp7InNvdXJjZXMiOiIzYTI4NjFhYi0xNWYwLTRjM2MtODZlMy0xNjNkMTY0ZDE0ZDYifX0.BWJXLworo44Nvfrh5a2hm_NIqgUoXTLjQlxyy7uaSWk
$ export PACKAGEMANAGER_ADDRESS=https://packagemanager.example.com
# Download the CLI
$ curl -fLOJH "Authorization: Bearer ${PACKAGEMANAGER_TOKEN}" "${PACKAGEMANAGER_ADDRESS}/__api__/download"
$ chmod +x rspm
Terminal
$ export PACKAGEMANAGER_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJwYWNrYWdlbWFuYWdlciIsImV4cCI6MTY1ODU4MjA3OCwianRpIjoiYmM5ZTg1NGYtNGNlNy00Zjc4LTlhMmMtZDliYzRlYTQ0NGVkIiwiaWF0IjoxNjU1OTkwMDc4LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQyNDIvIiwic2NvcGVzIjp7InNvdXJjZXMiOiIzYTI4NjFhYi0xNWYwLTRjM2MtODZlMy0xNjNkMTY0ZDE0ZDYifX0.BWJXLworo44Nvfrh5a2hm_NIqgUoXTLjQlxyy7uaSWk
$ export PACKAGEMANAGER_ADDRESS=https://packagemanager.example.com
# Download the CLI
$ curl -fLOJH "Authorization: Bearer ${PACKAGEMANAGER_TOKEN}" "${PACKAGEMANAGER_ADDRESS}/__api__/download?os=darwin"
$ chmod +x rspm
Powershell
$Env:PACKAGEMANAGER_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJwYWNrYWdlbWFuYWdlciIsImV4cCI6MTY1ODU4MjA3OCwianRpIjoiYmM5ZTg1NGYtNGNlNy00Zjc4LTlhMmMtZDliYzRlYTQ0NGVkIiwiaWF0IjoxNjU1OTkwMDc4LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQyNDIvIiwic2NvcGVzIjp7InNvdXJjZXMiOiIzYTI4NjFhYi0xNWYwLTRjM2MtODZlMy0xNjNkMTY0ZDE0ZDYifX0.BWJXLworo44Nvfrh5a2hm_NIqgUoXTLjQlxyy7uaSWk"
$Env:PACKAGEMANAGER_ADDRESS = "https://packagemanager.example.com"
# Download the CLI
$urlCLI = "$Env:PACKAGEMANAGER_ADDRESS/__api__/download?os=windows"
$outputCLI = "rspm.exe"
$wc = New-Object System.Net.WebClient
$wc.Headers['Authorization'] = "Bearer $Env:PACKAGEMANAGER_TOKEN"
$wc.DownloadFile($urlCLI, $outputCLI)
# Alternatively, on Windows 10 (v1803+), you can download the CLI with curl
curl -OutFile rspm.exe -Headers @{ Authorization = "Bearer $Env:PACKAGEMANAGER_TOKEN"} "$Env:PACKAGEMANAGER_ADDRESS/__api__/download?os=windows"
After the environment variables are set, the remote machine can use CLI commands that support remote use:
Terminal
./rspm add --source=local-python-src --path=/path/to/packages
Powershell
.\rspm.exe add --source=local-python-src --path=C:\path\to\packages
Uploading Python Packages Remotely with Twine
Twine is a tool that can be used to upload Python packages that have been built locally. To learn more about its full feature set, review the Twine documentation.
Other Python upload tools like Poetry and Flit should work since the Package Manager API attempts to maintain compatibility with the PyPI Warehouse upload endpoint. However, Twine is the officially recommended and documented method of uploading Python packages to Package Manager.
To start, install Twine on the system that will be uploading packages:
Terminal
pip install twine
Twine Configuration
There are several ways that Twine can be used to remotely authenticate with Package Manager:
- Using the
TWINE_*
environment variables. - Configuring a
.pypirc
file. - Authenticating with
keyring
.
Using the TWINE_*
environment variables
The three environment variables Twine uses are:
TWINE_REPOSITORY_URL
TWINE_USERNAME
TWINE_PASSWORD
The TWINE_REPOSITORY_URL
environment variable is the address that is used to upload to Package Manager remotely, ending with the endpoint /upload/pypi/{source-name}
. Since we are using token authentication, TWINE_USERNAME
will always be set to __token__
. Finally, TWINE_PASSWORD
is set to the token that was generated above. To put this all together, the following is an example of what the variables could be set to:
Terminal
$ export TWINE_REPOSITORY_URL=https://packagemanager.posit.co/upload/pypi/local-python-src
$ export TWINE_USERNAME=__token__
$ export TWINE_PASSWORD=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJwYWNrYWdlbWFuYWdlciIsImV4cCI6MTY1ODU4MjA3OCwianRpIjoiYmM5ZTg1NGYtNGNlNy00Zjc4LTlhMmMtZDliYzRlYTQ0NGVkIiwiaWF0IjoxNjU1OTkwMDc4LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQyNDIvIiwic2NvcGVzIjp7InNvdXJjZXMiOiIzYTI4NjFhYi0xNWYwLTRjM2MtODZlMy0xNjNkMTY0ZDE0ZDYifX0.BWJXLworo44Nvfrh5a2hm_NIqgUoXTLjQlxyy7uaSWk
With the environment variables set, you can easily upload with Twine:
Terminal
$ twine upload dist/*
Configuring a .pypirc
file
An alternative approach to using the Twine environment variables is to configure a .pypirc
file. Twine looks for this file in the ~/.pypirc
location by default. If instead a user wants to pass in a .pypirc
file that is not in that location, they can use Twine’s --config-file
flag to point to that file in a different directory.
The .pypirc
looks like the following:
~/.pypirc
[distutils]
index-servers =
package-manager
[package-manager]
repository = https://packagemanager.posit.co/upload/pypi/local-python-src
username = __token__
password = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJwYWNrYWdlbWFuYWdlciIsImV4cCI6MTY1ODU4MjA3OCwianRpIjoiYmM5ZTg1NGYtNGNlNy00Zjc4LTlhMmMtZDliYzRlYTQ0NGVkIiwiaWF0IjoxNjU1OTkwMDc4LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQyNDIvIiwic2NvcGVzIjp7InNvdXJjZXMiOiIzYTI4NjFhYi0xNWYwLTRjM2MtODZlMy0xNjNkMTY0ZDE0ZDYifX0.BWJXLworo44Nvfrh5a2hm_NIqgUoXTLjQlxyy7uaSWk
Then to use this file to upload with Twine:
Terminal
twine upload -r package-manager dist/*
The .pypirc
file makes it easy to upload to various sources quickly. When you create another repository definition in the .pypirc
file, it is made available to use with Twine’s -r
flag.
Authenticating with keyring
An alternative approach to putting the username/password directly into a .pypirc
file or environment variables is to store them securely in a keyring
. Keyring is automatically installed with Twine, so it is available to use out-of-the-box.
From keyring: macOS keychain support for macOS 11 (Big Sur) and later requires Python 3.8.7 or later with the “universal2” binary.
If a user is using the .pypirc
method, their file would now look like:
~/.pypirc
[distutils]
index-servers =
package-manager
[package-manager]
repository = https://packagemanager.posit.co/upload/pypi/local-python-src
If a user is instead using the Twine environment variables, now all they need to set is:
Terminal
export TWINE_REPOSITORY_URL=https://packagemanager.posit.co/upload/pypi/local-python-src
Then, to store the username and password for the repository, a user can save them with keyring
:
Terminal
$ keyring set https://packagemanager.posit.co/upload/pypi/local-python-src __token__
Password for '__token__' in 'https://packagemanager.posit.co/upload/pypi/local-python-src':
The password has been saved securely within the keyring
and can be used by Twine. When uploading with Twine, it will now prompt for the username and check keyring
to see if it has a password. If it does, it will successfully upload the distributions:
Terminal
$ cd directory/of/internal/example_package
$ twine upload -r package-manager dist/*
Enter your username: __token__
Uploading distributions to https://packagemanager.posit.co/upload/pypi/local-python-src
Uploading example_package.whl
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8.1/8.1 MB • 00:02 • 3.5 MB/s
Twine Package Signing
Twine and Package Manager also support uploading signed packages. Package Manager detects when a signed file is uploaded and stores the file with the Python package. A user can see this by navigating to the package in the UI and clicking on the specific distribution. If the distribution has a green SIGNED
label, then it successfully uploaded the .asc
file with the Python distribution. To download this file for future use, a user can append .asc
to the URL for that specific distribution to download the signed file.
There are two methods for uploading signed files:
- Manually uploading pre-signed files.
- Automatically signing with Twine.
Manually uploading pre-signed files
If you already have a package pre-signed, the Twine command is:
Terminal
twine upload example_package.whl example_package.whl.asc
Simply uploading with twine upload dist/*
will automatically upload any .asc
files that exist for the packages in that directory.
Automatically signing with Twine
There is also the --sign
flag that instructs Twine to sign the files before uploading:
Terminal
$ twine upload --sign example_package.whl
Uploading distributions to https://packagemanager.posit.co/upload/pypi/local-python-src
Signing example_package.whl
Uploading example_package.whl
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8.1/8.1 MB • 00:02 • 3.5 MB/s