Releasing¶
This guide describes how to release a new version of es-translator. Releases are automated: creating a GitHub release triggers a CI workflow that publishes the package to PyPI and pushes the Docker image to Docker Hub.
Only maintainers with push access to the repository can perform releases.
How releases work¶
A release is produced in three steps:
- Bump the version locally with
make bump-{patch,minor,major}. This updatespyproject.toml, creates a commit, and tags it. - Push the commit and tag to GitHub.
- Create a GitHub release for the tag. This fires the
release: publishedevent in.github/workflows/main.yml, which runs:- The full test matrix (
container-test-job) publish-pypi— builds the package with Poetry and uploads it to PyPI via OIDC (no token needed)publish-docker— buildsicij/es-translator:<tag>andicij/es-translator:latestand pushes them to Docker Hubpublish-docs— builds the MkDocs site and deploys it to GitHub Pages
- The full test matrix (
If any of those steps fail, the release is not published. You can re-run the failed job from the GitHub Actions UI once the cause is fixed.
One-time setup¶
Before the first automated release works, two things must be configured on the GitHub side.
PyPI trusted publisher¶
es-translator uses PyPI trusted publishing instead of a long-lived API token. Configure it once:
- Go to pypi.org/manage/project/es-translator/settings/publishing/
- Add a new "GitHub" publisher with:
- Owner:
ICIJ - Repository:
es-translator - Workflow filename:
main.yml - Environment name: (leave empty)
- Owner:
The publish-pypi job requests an OIDC token (permissions: id-token: write) which PyPI exchanges for short-lived upload credentials. No secret needs to be stored in GitHub.
Docker Hub credentials¶
Add two repository secrets in Settings → Secrets and variables → Actions:
DOCKERHUB_USERNAME— a Docker Hub account with write access toicij/es-translatorDOCKERHUB_TOKEN— a Docker Hub access token scoped toicij/es-translatorwith Read, Write, Delete permissions
GitHub Pages¶
The docs site is deployed via GitHub Actions (no gh-pages branch). One-time config:
- Go to Settings → Pages
- Under Build and deployment, set Source to GitHub Actions
The publish-docs job uploads the built site as a Pages artifact (actions/upload-pages-artifact) and deploys it with actions/deploy-pages using OIDC, so no token needs to be stored.
Release process¶
1. Verify tests pass¶
make lint
make test
2. Bump the version¶
Use the semver target that matches the change:
# 1.12.2 -> 1.12.3
make bump-patch
# 1.12.2 -> 1.13.0
make bump-minor
# 1.12.2 -> 2.0.0
make bump-major
Each target:
- Updates the version in
pyproject.tomlviapoetry version - Commits the change as
build: bump to <version> [skip ci](the marker skips the redundant push-triggered CI run; the release-triggered publish jobs are unaffected) - Tags the commit with
<version>(novprefix)
The Makefile prints the next steps on success.
3. Push the commit and tag¶
git push --follow-tags
4. Create the GitHub release¶
gh release create <version> --generate-notes
Or open https://github.com/ICIJ/es-translator/releases/new?tag=<version> and click Publish release.
As soon as the release is published, the workflow runs. Watch it at:
gh run watch
The PyPI package, Docker image, and docs site are all updated automatically when the workflow finishes.
Version numbering¶
es-translator follows Semantic Versioning:
| Change type | Version bump | Example |
|---|---|---|
| Bug fixes | PATCH | 1.12.2 → 1.12.3 |
| New features (backwards compatible) | MINOR | 1.12.2 → 1.13.0 |
| Breaking changes | MAJOR | 1.12.2 → 2.0.0 |
Makefile reference¶
| Target | Description |
|---|---|
make bump-patch |
Bump patch version (x.x.X) |
make bump-minor |
Bump minor version (x.X.0) |
make bump-major |
Bump major version (X.0.0) |
make build |
Build sdist and wheel into dist/ |
make distribute |
Build and publish to PyPI (manual fallback) |
make docker-setup-multiarch |
Configure buildx for multi-arch builds |
make docker-publish |
Build and push Docker image (manual fallback) |
Manual fallback¶
If the automated workflow is unavailable, you can publish from your machine.
Publish to PyPI manually¶
poetry config pypi-token.pypi <your-token>
make distribute
Publish the Docker image manually¶
# First-time setup for multi-arch builds
make docker-setup-multiarch
# Login and push
docker login
make docker-publish
make docker-publish reads the current version from poetry version -s and pushes both icij/es-translator:<version> and icij/es-translator:latest.
Publish the docs manually¶
Docs publish only via the publish-docs CI job on release. If it fails, re-run it from the GitHub Actions UI (or via gh workflow run). There is no local equivalent — Zensical does not ship a gh-deploy command.
Troubleshooting¶
publish-pypi fails with invalid-publisher¶
PyPI cannot find a trusted publisher matching this workflow. Check the publisher entry on PyPI:
- Repository owner is
ICIJ(case sensitive) - Workflow filename matches exactly (
main.yml) - Environment name is empty (the workflow does not set one)
publish-docker fails at the login step¶
The Docker Hub secrets are missing, expired, or scoped to the wrong repository. Regenerate the token and update DOCKERHUB_TOKEN in the repo settings.
Tag was created but I don't want to release it¶
git tag -d <version> # delete locally
git push --delete origin <version> # delete on GitHub
Then revert the version-bump commit (git revert <sha>) and push.
Re-running a failed publish¶
If only one of the publish jobs failed, open the failed run in the GitHub Actions UI and click Re-run failed jobs. The publish jobs are idempotent for the Docker image (overwrites the tag) but not for PyPI — PyPI rejects re-uploads of the same version. If the PyPI step partially succeeded, you must bump to a new version.