Lesson 05 of 8 · Level 2 — Working in a team
Branching strategies and releases
Choose how a team branches and releases: trunk-based development, GitHub flow, GitFlow and release branches compared; tagging releases with semantic versions; and handling hotfixes without losing fixes between branches.
Why the strategy matters
How a team branches decides how often code integrates, how much merging hurts, and how quickly a fix reaches production. For platforms that deploy continuously, simpler is better.
A shared kitchen works best when everyone puts ingredients back on the main shelf quickly. If each cook hoards their own shelf for weeks, the day they try to combine everything is chaos.
The common strategies
| Strategy | How it works | Fits | Watch out for |
|---|---|---|---|
| Trunk-based | Tiny branches (or direct commits) merged into main at least daily; feature flags hide unfinished work; main is always deployable |
Continuous delivery, strong CI, experienced teams | Needs good tests and feature flags |
| GitHub flow | Short-lived feature branch → pull request → merge to main → deploy |
Most web services and platform repos | Same as trunk-based, just with PRs |
| Release branches | Cut release/1.4 when stabilising; fixes flow main → release |
Products with supported versions, scheduled releases | Every fix may need applying to several branches |
| GitFlow | develop, feature/*, release/*, hotfix/*, main |
Boxed software with long release cycles | Heavy; long-lived branches drift; rarely worth it for services |
Recommendation for most infrastructure and service repositories: GitHub flow / trunk-based, deploying main through the pipeline, with release branches only where you genuinely support several versions at once.
Tags and semantic versions
Mark releases with annotated tags and semantic versions:
MAJOR.MINOR.PATCH 2.3.1
MAJOR breaking change (clients must change)
MINOR new, compatible feature
PATCH compatible bug fix
$ git tag -a v1.4.0 -m "Release 1.4.0: order search, faster checkout"
$ git push origin v1.4.0
$ git log v1.3.0..v1.4.0 --oneline # release notes source
Container images, Helm charts and Terraform modules should carry the same version as the tag that produced them, so anyone can go from a running artifact back to the exact source.
Hotfixes without losing fixes
The rule that prevents regressions: fix forward on main first, then carry the fix to older branches.
$ git switch main
# fix, PR, merge -> commit 7c1d2e3
$ git switch release/1.4
$ git cherry-pick -x 7c1d2e3 # -x records "cherry picked from commit 7c1d2e3"
$ git tag -a v1.4.1 -m "Hotfix: order 404"
$ git push origin release/1.4 v1.4.1
If the fix had to be made on the release branch first (production is burning), open a pull request to bring it to main immediately, and check with git branch --contains <sha>.
Try it: a release and a hotfix
- In a test repo, make a few commits on
mainand tagv1.0.0. - Create
release/1.0from the tag, add two more features onmain, and tagv1.1.0there. - Fix a "bug" on
main, cherry-pick it with-xontorelease/1.0, and tagv1.0.1. - Run
git log v1.0.0..v1.0.1 --onelineandgit branch --contains <fix-sha>.
Going deeper: release automation
Tools such as semantic-release or release-please read Conventional Commit messages (feat:, fix:, feat!:), decide the next version, write the changelog, tag and publish, removing human error from versioning. For GitOps repositories, the "release" is often a pull request that bumps an image tag or chart version per environment (see "GitOps Principles & Practice", lesson 03).
Recap
- Prefer trunk-based / GitHub flow for services and infrastructure; release branches only for supported versions; GitFlow rarely.
- Annotated tags with semantic versions; artifacts carry the same version.
- Hotfix: fix on main, then cherry-pick -x to release branches, tag a patch release.
This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.