Publishing with Poetry
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the Python Reference bibliography. |
Poetry builds and uploads in one tool: poetry build makes the sdist and wheel, poetry publish uploads the current version’s
files to PyPI or to any index that implements the legacy upload API. This page publishes the Poetry-flavoured birdtally to the
same local pypiserver used on
Publishing with build & twine
(localhost:8084, uploads require HTTP basic authentication), with Poetry 2.5.1 on 2026-10-22, on Linux with CPython 3.13.16 as
the project’s interpreter (hence the py3.13 virtualenvs; Poetry itself running on 3.14.6). TestPyPI and PyPI are not
used. Repositories and credentials in general are on
Repositories & Package Sources.
Configure the target
Upload targets are per-user configuration, not part of pyproject.toml:
poetry config repositories.local http://localhost:8084
poetry config repositories.local
export POETRY_HTTP_BASIC_LOCAL_USERNAME=birdwatch
export POETRY_HTTP_BASIC_LOCAL_PASSWORD=… # from your secret store
{'url': 'http://localhost:8084'}
-
The URL is the upload (legacy API) endpoint, not the
/simple/one. Since 2.4 Poetry adds a missing trailing slash, sohttp://localhost:8084works. -
Credentials:
poetry config http-basic.local <user>(prompts for the password; keyring orauth.toml),poetry config pypi-token.<name> <token>for token-based indexes, or the environment variablesPOETRY_HTTP_BASIC_<NAME>_USERNAME/_PASSWORDandPOETRY_PYPI_TOKEN_<NAME>in CI.pypiandtestpypiusepypi-token.pypi/pypi-token.testpypi. -
Where a system keyring is available,
http-basicstores the password in it (aspoetry-repository-<name>, for examplepoetry-repository-pypi) and only the username inauth.toml; if the keyring fails or is unsupported, the password goes intoauth.tomlin plain text.poetry config keyring.enabled false(POETRY_KEYRING_ENABLED) turns the keyring off on purpose, for example on a CI runner without one, where the environment variables are the better choice anyway. -
A password that starts with
-needspoetry config — http-basic.local user -secret; an empty password (some hosted indexes, such as Gemfury, use the token as the username) ispoetry config http-basic.local <token> "". -
Since 2.5.0 Poetry never sends credentials configured for an
httpsrepository overhttp.
Publish
With an old 0.1.0 build still in dist/ and the version bumped to 0.1.2:
poetry version 0.1.2
poetry publish -n -r local --build --dry-run
Bumping version from 0.1.0 to 0.1.2
Warning: There are 2 files ready for publishing in dist. Build anyway!
Creating virtualenv birdtally-CYLjbS5k-py3.13 in …/virtualenvs
Building birdtally (0.1.2)
Building sdist
- Building sdist
- Built birdtally-0.1.2.tar.gz
Building wheel
- Building wheel
- Built birdtally-0.1.2-py3-none-any.whl
Publishing birdtally (0.1.2) to local
- Uploading birdtally-0.1.2.tar.gz 0%
- Uploading birdtally-0.1.2.tar.gz 100%
- Uploading birdtally-0.1.2-py3-none-any.whl 0%
- Uploading birdtally-0.1.2-py3-none-any.whl 100%
Three details:
-
--buildbuilds first. Ifdist/already holds files, Poetry asksThere are 2 files ready for publishing in dist. Build anyway? (yes/no) [no]; without a terminal that prompt repeats forever, so pass-n(then it prints the warning above and builds) or cleandist/yourself. -
Only the files of the current version are uploaded: the 0.1.0 files were left alone. Since 2.4.0 a failed
--buildstops the publish instead of uploading stale files. -
--dry-rundoes everything except the upload, progress lines included. Without it, the same command uploaded both files, and the local index then listedbirdtally-0.1.2-py3-none-any.whlandbirdtally-0.1.2.tar.gznext to the earlier releases.
Publishing again fails, here with pypiserver’s 409 Conflict (twine got a 400 for the same situation):
Publishing birdtally (0.1.2) to local
- Uploading birdtally-0.1.2.tar.gz 0%
- Uploading birdtally-0.1.2.tar.gz 100%
HTTP Error 409: Conflict | b'\n <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">…<pre>Package 'birdtally-0.1.2.tar.gz' already exists!…'
Unlike twine, Poetry’s --skip-existing works against this index:
- Uploading birdtally-0.1.2.tar.gz 0%
- Uploading birdtally-0.1.2.tar.gz 100%
- Uploading birdtally-0.1.2.tar.gz File exists. Skipping
- Uploading birdtally-0.1.2-py3-none-any.whl 0%
- Uploading birdtally-0.1.2-py3-none-any.whl 100%
- Uploading birdtally-0.1.2-py3-none-any.whl File exists. Skipping
A wrong password ends in HTTP Error 403: Forbidden, exit code 1.
poetry publish options
| Option | Meaning |
|---|---|
|
The configured repository (default: PyPI). |
|
Credentials on the command line; prefer configuration or environment variables. |
|
A CA bundle and a client certificate (or set |
|
Where the files are (default |
|
Build before publishing. |
|
Do everything but upload. |
|
Ignore files the repository already has. |
Source: Poetry CLI: publish (options).
Certificates and mutual TLS
A private index behind a company certificate authority, or one that requires a client certificate (mutual TLS), is configured per
repository name. The certificate settings live with the credentials, in auth.toml in Poetry’s configuration directory, not in
config.toml:
poetry config repositories.internal https://pypi.internal.example.org/legacy/
poetry config certificates.internal.cert /etc/ssl/internal-ca.pem
poetry config certificates.internal.client-cert /etc/ssl/birdtally-client.pem
poetry config certificates.internal.cert
cat "$POETRY_CONFIG_DIR/auth.toml"
POETRY_CERTIFICATES_INTERNAL_CERT=/tmp/other-ca.pem poetry config --list | grep certificates
There is no certificates.internal.cert setting.
[certificates.internal]
cert = "/etc/ssl/internal-ca.pem"
client-cert = "/etc/ssl/birdtally-client.pem"
certificates.internal.cert = "/etc/ssl/internal-ca.pem" # /tmp/other-ca.pem
certificates.internal.client-cert = "/etc/ssl/birdtally-client.pem"
-
certificates.<name>.certis the CA bundle used to verify the repository’s server certificate;certificates.<name>.client-certis the client certificate Poetry presents. Either can be set without the other. -
The environment forms are
POETRY_CERTIFICATES_<NAME>_CERTandPOETRY_CERTIFICATES_<NAME>_CLIENT_CERT. As with other settings,--listshows the stored value and, after the#, the value the variable overrides it with. -
--listshows the certificate settings, but reading one by name fails withThere is no … setting.(exit code 1) in Poetry 2.5.1; use--listto check them. -
poetry publish --certand--client-certgive the same two files for a single run. -
certificates.<name>.cert falseskips TLS verification for that repository. The documentation warns against it; use it only for a throwaway index with a self-signed certificate, never for one you upload releases to.
The commands above only write and read the configuration (in a scratch POETRY_CONFIG_DIR); no upload to a TLS or mutual-TLS
index was run here. The host pypi.internal.example.org and the certificate paths are placeholders.
TestPyPI and PyPI (not run here)
poetry config repositories.testpypi https://test.pypi.org/legacy/
export POETRY_PYPI_TOKEN_TESTPYPI="$TESTPYPI_TOKEN"
poetry publish -n -r testpypi --build
export POETRY_PYPI_TOKEN_PYPI="$PYPI_TOKEN"
poetry publish -n --build
pypi needs no repositories entry. Poetry uses the token as the password with the __token__ username.
Source: Poetry: Publishing to PyPI, keyring.enabled.
No Trusted Publishing in Poetry
Poetry has no built-in support for PyPI Trusted Publishing or PEP 740 attestations: the request, python-poetry/poetry#7940, has been open since 2023-05-17. The documented way to combine them is to build with Poetry and upload with the PyPA action, which handles the OIDC exchange and the attestations:
- run: pipx install poetry==2.5.1
- run: poetry build
- uses: pypa/gh-action-pypi-publish@release/v1 # in a job with id-token: write
The complete workflow is on GitHub Actions & GitLab CI Release Workflows.
Source: python-poetry/poetry#7940.
Compared with build & twine
| Poetry | build + twine | |
|---|---|---|
Build |
|
|
Target |
|
|
Credentials |
keyring, |
keyring, |
Check before upload |
|
|
|
any index |
PyPI only |
Trusted Publishing, attestations |
no |
twine 6.1+ (preliminary), the PyPA action |
twine check also works on Poetry’s dist/: run it before poetry publish.