Release Management
Release management is the process of versioning, tagging, documenting, and publishing your software. Good release management makes deployments predictable, rollbacks traceable, and changelogs automatic.
Semantic Versioning (SemVer)
Section titled “Semantic Versioning (SemVer)”The most widely adopted versioning scheme:
MAJOR.MINOR.PATCH │ │ │ │ │ └── Bug fixes (backward compatible) │ └──────── New features (backward compatible) └────────────── Breaking changes (not backward compatible)
Examples: 1.0.0 → Initial release 1.1.0 → Added a new feature 1.1.1 → Fixed a bug 2.0.0 → Breaking API change| Change Type | Version Bump | Example |
|---|---|---|
| Bug fix, patch | 1.2.3 → 1.2.4 | Fix login timeout |
| New feature (backward compatible) | 1.2.3 → 1.3.0 | Add search endpoint |
| Breaking change | 1.2.3 → 2.0.0 | Remove deprecated API |
Pre-Release Versions
Section titled “Pre-Release Versions”2.0.0-alpha.1 First alpha2.0.0-beta.1 First beta2.0.0-rc.1 Release candidate2.0.0 Stable releaseSemVer Rules
Section titled “SemVer Rules”- Patch version MUST be incremented for backward-compatible bug fixes.
- Minor version MUST be incremented for backward-compatible new functionality.
- Major version MUST be incremented for any backward-incompatible changes.
- Once a version is released, its contents MUST NOT be modified — release a new version instead.
Conventional Commits and SemVer
Section titled “Conventional Commits and SemVer”Conventional Commits provide a structured commit message format that automation tools can parse to determine the version bump:
feat: add user search endpoint → minor bump (1.2.0 → 1.3.0)fix: correct login timeout handling → patch bump (1.2.0 → 1.2.1)feat!: redesign authentication API → major bump (1.2.0 → 2.0.0)
Format: <type>(<scope>): <description>
[optional body]
[optional footer(s)] BREAKING CHANGE: <description>| Commit Type | SemVer Bump |
|---|---|
fix: | Patch |
feat: | Minor |
feat!: or BREAKING CHANGE: footer | Major |
docs:, chore:, style:, refactor:, test: | No bump (or patch, configurable) |
Automated Release Tools
Section titled “Automated Release Tools”semantic-release
Section titled “semantic-release”semantic-release fully automates versioning, changelog, and publishing based on commit messages:
Commits since last release: feat: add search API fix: handle null response docs: update README
semantic-release determines: → Minor bump (feat present) → 1.3.0 → 1.4.0 → Generates CHANGELOG entry → Creates Git tag v1.4.0 → Creates GitHub release → Publishes to npm (if configured)npm install --save-dev semantic-release @semantic-release/changelog @semantic-release/git{ "branches": ["main"], "plugins": [ "@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator", "@semantic-release/changelog", "@semantic-release/npm", "@semantic-release/github", ["@semantic-release/git", { "assets": ["CHANGELOG.md", "package.json"], "message": "chore(release): ${nextRelease.version} [skip ci]" }] ]}GitHub Actions
Section titled “GitHub Actions”name: Releaseon: push: branches: [main]
permissions: contents: write issues: write pull-requests: write
jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 persist-credentials: false - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npx semantic-release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }}release-please (Google)
Section titled “release-please (Google)”release-please creates a release PR that accumulates changes. When merged, it creates the tag and GitHub release:
Commits accumulate on main: feat: add search fix: handle null
release-please opens a PR: "chore: release 1.4.0" - Updates CHANGELOG.md - Bumps version in package.json
When you merge the PR: → Git tag v1.4.0 created → GitHub release createdGitHub Actions
Section titled “GitHub Actions”name: Releaseon: push: branches: [main]
permissions: contents: write pull-requests: write
jobs: release-please: runs-on: ubuntu-latest outputs: release_created: ${{ steps.release.outputs.release_created }} tag_name: ${{ steps.release.outputs.tag_name }} steps: - uses: googleapis/release-please-action@v4 id: release with: release-type: node
publish: needs: release-please if: ${{ needs.release-please.outputs.release_created }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 registry-url: https://registry.npmjs.org - run: npm ci - run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}Changesets
Section titled “Changesets”Changesets is popular in monorepos — developers add changeset files with their PRs:
# Developer runs this when making a changenpx changeset add# Prompts: What packages changed? Major/minor/patch? Description?# Creates .changeset/funny-llamas-swim.md---"@myorg/api": minor"@myorg/shared": patch---
Add user search endpoint to the API.When ready to release, a bot PR accumulates changesets and bumps versions.
Tool Comparison
Section titled “Tool Comparison”| Feature | semantic-release | release-please | Changesets |
|---|---|---|---|
| Version source | Commit messages | Commit messages | Changeset files |
| Human review | No (fully automatic) | Yes (release PR) | Yes (changeset PR) |
| Monorepo | Via plugins | Yes (manifest mode) | Yes (native) |
| Changelog | Auto-generated | Auto-generated | Auto-generated |
| Publish | npm, GitHub, Docker, etc. | GitHub release (publish separately) | npm (native) |
| Best for | Libraries, single packages | Any project wanting review step | Monorepos |
Git Tagging
Section titled “Git Tagging”Tags mark specific commits as releases:
# Annotated tag (recommended for releases)git tag -a v1.4.0 -m "Release 1.4.0: add search API, fix login timeout"git push origin v1.4.0
# List tagsgit tag -l "v1.*"
# Delete a taggit tag -d v1.4.0git push origin --delete v1.4.0Tagging Conventions
Section titled “Tagging Conventions”| Convention | Example | Use Case |
|---|---|---|
v prefix | v1.4.0 | Most common for application releases |
| No prefix | 1.4.0 | Some package managers prefer this |
| Package scoped | @myorg/[email protected] | Monorepo per-package tags |
| Date-based | 2026.02.17 | CalVer (calendar versioning) |
Triggering Pipelines on Tags
Section titled “Triggering Pipelines on Tags”# GitHub Actions — run on version tagson: push: tags: - 'v*' # v1.0.0, v2.1.3, etc.
# GitLab CIrelease: rules: - if: $CI_COMMIT_TAG =~ /^v\d+/Changelogs
Section titled “Changelogs”Auto-Generated Changelog
Section titled “Auto-Generated Changelog”Most release tools generate changelogs from commit messages:
# Changelog
## [1.4.0] - 2026-02-17
### Features- Add user search endpoint (#123)- Support pagination in list API (#125)
### Bug Fixes- Fix login timeout handling (#128)- Correct null response in profile API (#130)
## [1.3.0] - 2026-02-10...Keep a Changelog Format
Section titled “Keep a Changelog Format”The Keep a Changelog convention uses these categories:
| Category | What Goes Here |
|---|---|
| Added | New features |
| Changed | Changes to existing functionality |
| Deprecated | Features that will be removed |
| Removed | Features that were removed |
| Fixed | Bug fixes |
| Security | Vulnerability fixes |
GitHub and GitLab Releases
Section titled “GitHub and GitLab Releases”GitHub Releases
Section titled “GitHub Releases”# Create a release via CLIgh release create v1.4.0 \ --title "v1.4.0" \ --notes "## What's Changed- Add search API (#123)- Fix login timeout (#128)" \ --target main
# Upload release assetsgh release upload v1.4.0 ./dist/myapp-linux-amd64 ./dist/myapp-darwin-amd64
# Auto-generate release notes from PRsgh release create v1.4.0 --generate-notesGitHub can auto-generate release notes from merged PRs — grouped by labels.
GitLab Releases
Section titled “GitLab Releases”release: stage: deploy image: registry.gitlab.com/gitlab-org/release-cli:latest rules: - if: $CI_COMMIT_TAG =~ /^v\d+/ script: - echo "Creating release for $CI_COMMIT_TAG" release: tag_name: $CI_COMMIT_TAG name: "Release $CI_COMMIT_TAG" description: ./CHANGELOG.md assets: links: - name: Linux Binary url: https://example.com/myapp-linux-amd64Package Publishing
Section titled “Package Publishing”publish-npm: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 registry-url: https://registry.npmjs.org - run: npm ci - run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}publish-pypi: runs-on: ubuntu-latest permissions: id-token: write # OIDC (Trusted Publishers) steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - run: pip install build - run: python -m build - uses: pypa/gh-action-pypi-publish@release/v1 # Uses OIDC — no API token needed (Trusted Publisher configured on PyPI)Docker Images
Section titled “Docker Images”publish-docker: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/build-push-action@v6 with: push: true tags: | ghcr.io/${{ github.repository }}:${{ github.ref_name }} ghcr.io/${{ github.repository }}:latestRelease Strategies
Section titled “Release Strategies”Continuous Release (Every Merge to Main)
Section titled “Continuous Release (Every Merge to Main)”main: commit ──► CI ──► tag v1.4.0 ──► publish commit ──► CI ──► tag v1.4.1 ──► publishBest for libraries, APIs, SaaS. Every merge to main is a potential release.
Scheduled Release (Release Train)
Section titled “Scheduled Release (Release Train)”main: commit ──► commit ──► commit ──► Release cut (every 2 weeks) ──► tag v1.4.0 ──► publishBest for products with coordinated releases, QA cycles, or marketing announcements.
Release Branches
Section titled “Release Branches”main ────────●──────●──────●──────●──────●───► \ \ ▼ ▼ release/1.4 ──● ──● release/1.5 ──● (hotfixes) (hotfixes)Best for software that maintains multiple versions simultaneously (e.g. open-source libraries).
Calendar Versioning (CalVer)
Section titled “Calendar Versioning (CalVer)”Some projects use date-based versioning instead of SemVer:
2026.02.17 (YYYY.MM.DD)2026.2.1 (YYYY.M.micro)26.2 (YY.M)| Project | CalVer Format |
|---|---|
| Ubuntu | YY.MM (e.g. 24.04) |
| pip | YY.N (e.g. 24.0) |
| Terraform | MAJOR.MINOR.PATCH (SemVer, but frequent) |
| Docker Desktop | MAJOR.MINOR.PATCH (SemVer) |
CalVer works well for projects where compatibility is less relevant than recency (e.g. operating systems, infrastructure tools).
Key Takeaways
Section titled “Key Takeaways”- Semantic Versioning (MAJOR.MINOR.PATCH) communicates the impact of changes to consumers.
- Conventional Commits enable automated version bumps —
feat:= minor,fix:= patch,feat!:= major. - semantic-release is fully automatic; release-please adds a review step via PR; Changesets is best for monorepos.
- Git tags mark release points — trigger pipelines with tag-based triggers (
v*). - Changelogs should be auto-generated from commits or PRs — not hand-written.
- GitHub/GitLab releases combine a tag with release notes and downloadable assets.
- Choose a release strategy: continuous (every merge), scheduled (release train), or branched (multiple versions).