Releases follow the pattern v{SEMVER}-{COACH} (e.g., v1.0.0-ancelotti). Codenames are drawn alphabetically from the famous coach list.
Branch protection prevents direct pushes to master, so all release prep goes through a PR:
git checkout master && git pull
git checkout -b release/v1.0.0-ancelottiMove items from [Unreleased] to a new release section in CHANGELOG.md, then commit and push the branch:
# Move items from [Unreleased] to new release section
# Example: [1.0.0 - Ancelotti] - 2026-XX-XX
git add CHANGELOG.md
git commit -m "docs(changelog): prepare release notes for v1.0.0-ancelotti"
git push origin release/v1.0.0-ancelottiOpen a pull request from release/v1.0.0-ancelotti into master and merge it. The tag must be created after the merge so it points to the correct commit on master.
Before creating the tag, verify all of the following:
-
CHANGELOG.md[Unreleased]section is moved to a new versioned release entry - Release PR is merged into
master -
uv run pytestpasses - Coach name is valid and follows alphabetical order (see "Release Naming Convention" in CHANGELOG.md)
- All CI checks on
masterare green
After the PR is merged, pull master and create the annotated tag:
git checkout master && git pull
git tag -a v1.0.0-ancelotti -m "Release 1.0.0 - Ancelotti"
git push origin v1.0.0-ancelottiPushing the tag triggers the CD workflow which automatically:
- Validates tag format (semver and coach name)
- Builds and tests the project with coverage
- Publishes Docker images to GitHub Container Registry with three tags
- Creates a GitHub Release with auto-generated changelog from commits
Each release publishes multiple tags for flexibility:
# By semantic version (recommended for production)
docker pull ghcr.io/nanotaboada/python-samples-fastapi-restful:1.0.0
# By coach name (memorable alternative)
docker pull ghcr.io/nanotaboada/python-samples-fastapi-restful:ancelotti
# Latest release
docker pull ghcr.io/nanotaboada/python-samples-fastapi-restful:latest