Release Management and Versioning¶
Overview¶
Releases turn commits into consumable versions customers and operators can trust. Semantic Versioning (SemVer) tags (v1.4.2), GitHub Releases with notes and binaries, and changelog automation from conventional commits keep delivery auditable and repeatable from GitHub Actions.
This is Tutorial 13 in Module 13: Release Management of the REBASH Academy GitHub Actions for Cloud & DevOps Engineers series — written for Cloud, DevOps, Platform, SRE, and software engineers.
Prerequisites¶
- Testing in GitHub Actions
- Git — tags and releases
- GitHub CLI (
gh) optional for local release simulation
Learning Objectives¶
By the end of this tutorial, you will be able to:
- Apply SemVer tagging rules in CI (
major.minor.patch) - Create GitHub Releases with
gh release createorsoftprops/action-gh-release - Generate changelog sections from git history or conventional commits
- Gate releases on tests and security scans
- Attach build artefacts (binaries, SBOM) to releases
Architecture¶
Tag push or manual dispatch triggers release job after quality gates; changelog and assets publish to GitHub Releases.
Theory¶
What it is¶
| Element | Role |
|---|---|
| SemVer tag | Immutable pointer (v2.1.0) consumers pin |
| GitHub Release | User-facing page + asset downloads |
| Changelog | Human-readable what changed |
| Release workflow | Automates tag, notes, artefact upload |
| Draft/pre-release | Staging release before GA |
SemVer rules (summary): MAJOR breaking, MINOR features backward compatible, PATCH fixes. Pre-release: v1.0.0-rc.1.
Why it matters¶
Floating main deploys are hard to roll back and support. Tags give operators a known version. Automated changelogs reduce release-day toil. Attachments (container digest, SBOM, binary) prove what shipped.
How it works¶
- Merge to
mainwith conventional commits or maintainedCHANGELOG.md. - Workflow on
push: tags: v*orworkflow_dispatchwith version input. - Run tests and security jobs (
needs:) before release. - Build artefacts; compute release notes (
git log,github-script, or release-changelog-builder). - Create annotated tag if not present;
gh release createwith--notes-fileand--attach. - Deploy workflows trigger on
release: publishedevent.
Example trigger (documentation):
Key concepts and comparisons¶
| Approach | Pros | Cons |
|---|---|---|
| Manual tag + release | Simple | Error-prone, inconsistent notes |
| Tag push triggers CI | Git as source of truth | Requires tag discipline |
| Release please / semantic-release | Automated bump | Setup learning curve |
| Draft releases | Review before publish | Extra step |
Common pitfalls¶
- Tagging without running tests on the tagged commit.
- Reusing deleted tag names — breaks consumers caching old version.
- Changelog includes internal ticket noise only — no user impact.
contents: writeon every workflow — over-permissioned forks risk.- Release assets not retained — compliance gap.
Hands-on Lab¶
Objective¶
Simulate SemVer tagging locally, author a release workflow stub triggered by version tags, generate a changelog file from git history, and validate offline.
Prerequisites¶
- Git repository initialised in lab folder (local only is fine)
- Python 3 with PyYAML
Lab environment¶
Workspace: ~/rebash-github-actions/module-13
mkdir -p ~/rebash-github-actions/module-13/.github/workflows && cd ~/rebash-github-actions/module-13
set -euo pipefail
git init -q 2>/dev/null || true
Real-world scenario¶
Platform requires every production release to be a SemVer tag, GitHub Release with generated notes, and attached evidence tarball — only after tests pass.
Step-by-step tasks¶
Task 1 – Local SemVer tag practice¶
cd ~/rebash-github-actions/module-13
set -euo pipefail
echo 'module 13 release lab' > README.md
git add README.md 2>/dev/null || true
git -c user.email='lab@rebash.local' -c user.name='lab' commit -m 'chore: init module-13 lab' 2>/dev/null || true
git tag -a v0.1.0 -m 'lab patch release' 2>/dev/null || git tag v0.1.0
git tag -l 'v*' | tee tags.txt
Expected output
v0.1.0 listed in tags.txt.
Task 2 – Changelog generator script¶
Create generate-changelog.sh:
#!/usr/bin/env bash
set -euo pipefail
prev="${1:-}"
out="${2:-CHANGELOG.md}"
{
echo "# Changelog"
echo
echo "## Unreleased"
if [[ -n "$prev" ]] && git rev-parse "$prev" >/dev/null 2>&1; then
git log --pretty=format:'- %s (%h)' "${prev}..HEAD"
else
git log --pretty=format:'- %s (%h)' -n 10
fi
} > "$out"
echo "wrote $out"
Run it:
cd ~/rebash-github-actions/module-13
set -euo pipefail
chmod +x generate-changelog.sh
./generate-changelog.sh '' CHANGELOG.md | tee changelog-gen.txt
test -s CHANGELOG.md
head -5 CHANGELOG.md
Expected output
Non-empty CHANGELOG.md with commit subjects.
Task 3 – Release workflow stub¶
Create .github/workflows/release.yml:
name: Release
on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
inputs:
version:
description: 'SemVer tag without v prefix (e.g. 1.2.3)'
required: true
permissions:
contents: write
jobs:
test-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "Replace with real test workflow needs"
- run: test -f README.md
release:
needs: test-gate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build release evidence
run: |
tar -czf release-evidence.tgz README.md CHANGELOG.md 2>/dev/null || tar -czf release-evidence.tgz README.md
- name: Generate changelog
run: |
./generate-changelog.sh "$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo '')" RELEASE_NOTES.md
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${GITHUB_REF_NAME}"
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
TAG="v${{ inputs.version }}"
fi
gh release create "$TAG" release-evidence.tgz --notes-file RELEASE_NOTES.md --title "$TAG" || echo "gh not authenticated — stub OK locally"
Validate offline:
cd ~/rebash-github-actions/module-13
set -euo pipefail
python3 -c "import yaml; yaml.safe_load(open('.github/workflows/release.yml')); print('release workflow OK')"
grep -q "tags:" .github/workflows/release.yml
grep -q 'needs: test-gate' .github/workflows/release.yml
Expected output
release workflow OK; test gate and tag trigger present.
Task 4 – Offline validation bundle¶
cd ~/rebash-github-actions/module-13
set -euo pipefail
tar -czf module-13-evidence.tgz .github/workflows/release.yml generate-changelog.sh CHANGELOG.md tags.txt
ls -l module-13-evidence.tgz | tee evidence.txt
Expected output
Evidence archive created.
Validation steps¶
- SemVer tag exists locally (
v0.1.0) - Changelog generator produces non-empty output
- Release workflow parses;
needs: test-gatebefore release job - Tag push trigger
v*.*.*configured
Common errors and fixes¶
| Error | Cause | Fix |
|---|---|---|
gh: not authenticated | No token locally | Expected offline; use GITHUB_TOKEN in CI |
| Release on wrong commit | Shallow checkout | fetch-depth: 0 |
| Duplicate tag | Tag already exists | Bump version; never retag published releases |
| Empty notes | No commits since tag | Fix range in changelog script |
| Assets missing | Wrong path in gh release create | Verify artefact path before upload |
Challenge exercise¶
Split release into two workflows: (1) workflow_dispatch creates a draft release; (2) manual approval job publishes it. Document how this maps to GitHub Environments.
Learning outcomes¶
- Practised SemVer tagging locally
- Generated changelog from git history
- Authored gated release workflow with artefact attach
- Understood
contents: writescope for releases
Cleanup¶
# Keep lab artefacts for portfolio; delete tag locally if rehearsing:
# git tag -d v0.1.0
ls ~/rebash-github-actions/module-13
Validation¶
- Lab completed under
~/rebash-github-actions/module-13/ - You can explain MAJOR/MINOR/PATCH bump rules
- You can describe why releases need test gates
- You can name one release rollback strategy (redeploy prior tag)
Code Walkthrough¶
- Tag is contract — consumers pin
v1.2.3, not branch names. - Full git history — changelog and notes need
fetch-depth: 0. - Test before release —
needs:test/security jobs. - Attach evidence — SBOM, binaries, Terraform plan summaries.
- Least privilege —
contents: writeonly on release job.
Security Considerations¶
GITHUB_TOKENwithcontents: writecan push tags — restrict who can run release workflows.- Do not attach secrets or
.envfiles to release assets. - Verify tag signature or protected tag rules for production repos.
- Fork workflows must not create releases with elevated tokens.
- Scan release artefacts (Module 11) before publish.
Common Mistakes¶
Release from untested commit
Broken tag in production. Fix: require CI green on tagged SHA.
Floating changelog edits only in UI
Drift from git truth. Fix: generate notes from git/conventional commits in workflow.
Reusing tag after hotfix mistake
Consumers cache corrupted version. Fix: new patch version always.
Overbroad contents: write
Any job can tag. Fix: isolate release job; use environments.
Best Practices¶
- Protect
mainand tag patterns; require reviews. - Use pre-release tags (
-rc.1) for staging validation. - Link release notes to deployment workflow inputs (image digest per tag).
- Keep
CHANGELOG.mdin repo for human curation plus automated section. - Notify Slack/Teams on
release: publishedevent.
Troubleshooting¶
| Symptom | Likely cause | Fix |
|---|---|---|
| Workflow not on tag push | Pattern mismatch | Use v*.*.* or exact tag filter |
gh release 403 | Token permissions | permissions: contents: write |
| Wrong commits in notes | Shallow clone | fetch-depth: 0 |
| Asset upload fails | File > limit | Split assets or use object storage |
| Double release | Retagged push | Enable immutable releases policy |
Summary¶
Release management ties SemVer tags, automated changelogs, and GitHub Releases to tested artefacts. Gate publishing on CI and attach evidence for audit. Next: Composite Actions and Reusable Workflows.
Interview Questions¶
1. What do MAJOR, MINOR, and PATCH mean in SemVer?
Reveal answer
MAJOR increments for breaking API/behaviour changes, MINOR for backward-compatible features, PATCH for backward-compatible bug fixes — communicated as MAJOR.MINOR.PATCH.
2. Why trigger release workflows on tag push rather than manual UI only?
Reveal answer
Tag push keeps release steps in version-controlled workflows — repeatable, reviewable, and able to run tests before creating the GitHub Release.
3. What permission does gh release create typically need?
Reveal answer
contents: write on GITHUB_TOKEN (or a PAT with contents scope) to create releases and upload assets.
4. Why use fetch-depth: 0 in release jobs?
Reveal answer
Full history enables accurate changelog generation between tags and access to prior tags for comparison.
5. How do conventional commits help changelogs?
Reveal answer
Prefixes like feat: and fix: classify commits so automation groups features vs fixes in release notes without manual sorting.
6. What is the difference between a draft and pre-release?
Reveal answer
Draft releases are unpublished until explicitly released; pre-releases are published but marked unstable (e.g. -rc.1) for early adopters.
7. How should rollback relate to release tags?
Reveal answer
Redeploy the previous known-good tag/digest — tags make rollback a concrete operation rather than guessing a commit on main.
8. Why gate release jobs on test workflows?
Reveal answer
A tag must not ship broken artefacts; needs: ensures the exact commit passed unit/integration/E2E and security scans before assets publish.
Related Tutorials¶
- Testing in GitHub Actions
- Docker Pipelines with GitHub Actions
- Git — repository management and releases