PyPI
The proxy implements the PyPI simple-index protocol, so any client that speaks it can install through ShieldedStack.
Endpoint: <proxy-url>/pip/
Authentication: the API key, sent as the password in HTTP basic auth. The username is not checked; _ is the conventional placeholder.
Python has no shared registry configuration. pip reads pip.conf, and none of the other clients read it: uv has its own configuration and ignores pip’s even under uv pip install, Poetry keeps sources in pyproject.toml with credentials in a separate store, and Pipenv puts the index in the Pipfile. Configuring one tool does not configure the others, so each is written out below.
The index URL carries the credential, so the shape is the same everywhere pip accepts one:
https://_:YOUR_API_KEY_HERE@<proxy-host>/pip/On the command line:
pip install --index-url https://_:YOUR_API_KEY_HERE@<proxy-host>/pip/ package-nameThis puts a secret in your shell history. It is fine for a one-off check and wrong as a habit.
In a configuration file, which is the better default. On Linux and macOS that is ~/.config/pip/pip.conf, on Windows %APPDATA%\pip\pip.ini:
[global]index-url = https://_:YOUR_API_KEY_HERE@<proxy-host>/pip/Against an HTTP-only deployment, pip also needs the host marked as trusted:
[global]index-url = http://_:YOUR_API_KEY_HERE@<proxy-host-and-port>/pip/trusted-host = <proxy-host>trusted-host turns off TLS certificate verification for that host. Use it only for an HTTP-only deployment, or temporarily while an internal certificate is being trusted properly.
uv does not read pip.conf or pip.ini. That holds for uv pip install as well, which reimplements pip’s interface rather than pip’s configuration, so a workstation with a working pip setup still resolves from PyPI under uv until uv is configured separately.
Declare the index in pyproject.toml:
[[tool.uv.index]]name = "shieldedstack"url = "https://<proxy-host>/pip/"default = truedefault = true is the line that matters. Without it uv keeps PyPI in the list alongside the proxy, and anything the proxy does not answer for resolves from PyPI instead of failing.
The URL above carries no credential, so the file is safe to commit. uv takes the key from environment variables named after the index:
export UV_INDEX_SHIELDEDSTACK_USERNAME=_export UV_INDEX_SHIELDEDSTACK_PASSWORD=YOUR_API_KEY_HEREThe middle segment is the index name uppercased. Renaming the index renames the variables.
For a machine-wide setting outside any project, the same block goes in ~/.config/uv/uv.toml as [[index]], without the tool.uv prefix. To configure uv entirely from the environment, with no file at all:
export UV_DEFAULT_INDEX=https://_:YOUR_API_KEY_HERE@<proxy-host>/pip/UV_INDEX_URL is the older pip-compatible name for the same thing and still works.
Against an HTTP-only deployment, or an internal certificate uv does not trust yet:
[tool.uv]allow-insecure-host = ["<proxy-host>"]Or --allow-insecure-host <proxy-host> for a single command. As with pip’s trusted-host, this weakens transport security and belongs in local testing only.
Poetry
Section titled “Poetry”Poetry keeps the source and the credential in different places, which is why it takes two commands:
poetry source add --priority=primary shieldedstack https://<proxy-host>/pip/poetry config http-basic.shieldedstack _ YOUR_API_KEY_HEREThe first writes a [[tool.poetry.source]] block into pyproject.toml and holds no secret. The second writes to Poetry’s auth store, auth.toml or the system keyring, which is per machine and never committed.
--priority=primary displaces the implicit PyPI source. Confirm it actually did, because a source added at another priority leaves PyPI in the resolution order:
poetry source showIf PyPI is still listed as a configured source, remove it with poetry source remove pypi.
In CI, supply the credential through the environment instead of the auth store:
export POETRY_HTTP_BASIC_SHIELDEDSTACK_USERNAME=_export POETRY_HTTP_BASIC_SHIELDEDSTACK_PASSWORD=$SHIELDEDSTACK_API_KEYThe source name is uppercased and any dashes become underscores.
On a runner with no keyring daemon, Poetry can stall or fail while looking for one before it ever reaches the environment variables. Disable the lookup:
export PYTHON_KEYRING_BACKEND=keyring.backends.fail.KeyringPipenv
Section titled “Pipenv”Pipenv reads the index from the Pipfile. Replace the default pypi source rather than adding to it, because with both present a package can be satisfied from either:
[[source]]name = "shieldedstack"url = "https://_:${SHIELDEDSTACK_API_KEY}@<proxy-host>/pip/"verify_ssl = truePipenv expands ${SHIELDEDSTACK_API_KEY} when it reads the file, so the Pipfile stays committable with no key in it.
Pipfile.lock records which source each package came from, so a lock file produced before the change still points at PyPI. Re-lock after editing the source:
pipenv lockSet verify_ssl = false only for an HTTP-only deployment.
Committing the configuration
Section titled “Committing the configuration”Only pip needs a template, because pip is the one that has no way to keep the key out of the file:
-
pip. The credential lives inside the index URL, so a
pip.confwith a real key must never be committed. Addpip.confandpip.inito.gitignoreand commit apip.conf.templateinstead:[global]index-url = https://_:${SHIELDEDSTACK_API_KEY}@<proxy-host>/pip/Generate the real file from it:
Terminal window mkdir -p ~/.config/pipenvsubst < pip.conf.template > ~/.config/pip/pip.confIn CI, where no file needs to persist, pass the flag instead:
Terminal window pip install --index-url https://_:$SHIELDEDSTACK_API_KEY@<proxy-host>/pip/ -r requirements.txt -
uv. Commit the
[[tool.uv.index]]block as written. It holds a URL and no key, andUV_INDEX_SHIELDEDSTACK_PASSWORDsupplies the credential at runtime. -
Poetry. Commit the
pyproject.tomlsource block. The credential stays in the auth store on a workstation and inPOETRY_HTTP_BASIC_*variables in CI. -
Pipenv. Commit the
Pipfileas written. The${SHIELDEDSTACK_API_KEY}reference is expanded at read time, so the same file works on a workstation and a build agent.
Verify
Section titled “Verify”pip install requestsuv add requestspoetry add requestspipenv install requestsThe installed package appears under Packages in the Portal, attributed to the project name on the API key.
Names in package filters
Section titled “Names in package filters”Names are compared the way PyPI compares them: case is ignored, and any run of -, _ and . counts as a single -. Flask_Cors, flask.cors and flask-cors are one package, so a filter on any of them is saved and shown as flask-cors and matches all three. Wildcards follow the same rule: flask_* matches Flask-Cors. See Package names for how every ecosystem compares names.
Troubleshooting
Section titled “Troubleshooting”401 or 403. Check the credential reaches the client at all. For pip and Pipenv it has to be embedded in the index URL in the https://_:KEY@host/pip/ form; for uv and Poetry it comes from environment variables whose names are derived from the source name, so a renamed source silently drops the credential. Then check the key has not expired.
uv or Poetry installs from PyPI even though pip is configured. Expected. Neither reads pip.conf. Configure them as above.
uv resolves some packages from PyPI. default = true is missing from the index block, so PyPI is still in the search list as a fallback.
Poetry still resolves from PyPI. Run poetry source show. The source needs primary priority, and an explicitly configured pypi source needs removing.
Poetry hangs or fails before authenticating in CI. It is looking for a system keyring the runner does not have. Set PYTHON_KEYRING_BACKEND=keyring.backends.fail.Keyring.
Pipenv installs from PyPI despite the Pipfile. The lock file predates the source change. Run pipenv lock, and confirm the default pypi source was replaced rather than left alongside the new one.
Certificate errors. Add trusted-host = <proxy-host> for pip, allow-insecure-host = ["<proxy-host>"] for uv, or verify_ssl = false in the Pipfile source for Pipenv. All three are for an HTTP-only deployment or an internal certificate that is not trusted yet.
Configuration seems ignored. pip reads from several locations depending on platform and virtualenv, and uv reads project configuration before user configuration. Confirm the file you edited is the one in effect, and fall back to the command-line flag to isolate the problem.