Skip to content

← Writing

engineering

Trunk-Based vs Git Flow vs GitHub Flow

· Jerwin Arnado ·

A branching model is the set of rules your team agrees on for how a change travels from a developer’s machine to production. Pick the wrong one and you either drown in long-lived branches and painful merges, or you ship chaos with no release discipline. Three models dominate the conversation — Git Flow, GitHub Flow, and trunk-based development — and they sit on a spectrum from heavyweight to minimal. Here’s each, honestly, with the team it actually fits.

Git Flow: the heavyweight

Proposed by Vincent Driessen in 2010, Git Flow uses multiple long-lived branches with strict roles:

main      ──────●────────────────●──────   (production, tagged releases only)
develop   ──●──●──●──●──●──●──●──●──────    (integration branch)
feature/*    └──┘  └──┘                     (branch off develop, merge back)
release/*              └────┘               (stabilize, then merge to main + develop)
hotfix/*                        └──┘        (branch off main, patch, merge both)
  • main holds production; every commit is a tagged release.
  • develop is the integration branch where features land.
  • feature/* branches off develop and merges back.
  • release/* branches stabilize a version before it hits main.
  • hotfix/* patches production directly off main.

Fits: software with explicit versioned releases — desktop apps, mobile apps, libraries, anything where multiple versions exist in the wild and you ship on a schedule rather than continuously.

The cost: it’s a lot of process. The develop-vs-main split and the release/hotfix dance are overhead that only pays off when you genuinely have versioned releases to manage. For a web app that deploys continuously, it’s pure friction — even Driessen later added a note that Git Flow is not for continuous-delivery web apps.

GitHub Flow: the middleweight

GitHub’s own model strips it to one rule: main is always deployable, everything else is a short-lived branch.

main    ──●──────●──────●──────●──   (always deployable)
feature   └─PR─┘  └─PR─┘             (branch, PR, review, merge, deploy)

The loop:

  1. Branch off main for any change (feature/passkey-login).
  2. Commit, push, open a pull request.
  3. Review, CI runs, discussion happens on the PR.
  4. Merge to main — and deploy, immediately or on the next release.
  5. Delete the branch.

No develop, no release branches. Fits: web apps and services that deploy frequently — which is most of what I build. It keeps the PR-review checkpoint (where signed commits and CI gates live) without Git Flow’s branch sprawl. For a small team shipping a SaaS, this is usually the sweet spot.

Trunk-based development: the lightweight

The model behind continuous integration at scale: everyone commits to one branch (main/trunk), and branches live hours, not days.

main  ──●─●─●─●─●─●─●─●─●──   (everyone integrates here, constantly)

Changes are tiny and merged the same day — often behind feature flags so incomplete work can land on main without being active in production. Releases are cut from trunk by tagging, not by maintaining a separate branch.

Fits: teams with strong CI and test coverage practicing real continuous delivery — and, ironically, solo developers, for whom “trunk-based” just means “commit to main, don’t bother branching for every little thing.”

The cost: it demands discipline the model doesn’t provide on its own. Without solid automated tests and feature flags, “everyone commits to main constantly” is a recipe for a broken trunk. The process is light precisely because the engineering underneath it is heavy.

Picking one

Model Branches Fits The tax
Git Flow Many, long-lived Versioned releases (apps, libraries) Heavy process overhead
GitHub Flow One main + short PRs Continuously-deployed web apps Needs deploy automation
Trunk-based One trunk, hours-long High-CI continuous delivery; solo devs Needs tests + feature flags

The honest take for small teams: you’re probably overthinking it. The number of two-person teams running full Git Flow — with a develop branch, release branches, the whole ceremony — for a web app that deploys on every merge is genuinely large, and it’s almost always wasted motion. Short-lived feature branches off main, reviewed via PR, merged and deployed (GitHub Flow) covers the vast majority of web work. Reach for Git Flow only when you actually ship versioned releases; reach for strict trunk-based when your CI is strong enough to trust it.

Caveats and best practices

  • Branch lifetime is the real metric. Whatever you call your model, the thing that predicts merge pain is how long branches live. Short branches merge cleanly; week-old branches drift into conflict territory. Optimize for short, no matter the model.
  • Match the model to your deploy cadence, not the other way around. Continuous deploy → GitHub Flow or trunk. Scheduled versioned releases → Git Flow. Let how you ship decide how you branch.
  • Protect main regardless. Required PR reviews, passing CI, and (where it matters) signed commits should gate main in every model.
  • Feature flags decouple “merged” from “released.” They’re what makes trunk-based safe and what lets GitHub Flow merge unfinished-but-inert work — worth adopting before you “need” them.

Conclusion

Three models, one underlying question: how does code get to production, and how often? Git Flow manages versions; GitHub Flow manages continuous deploys; trunk-based manages constant integration. Most small teams shipping web apps want GitHub Flow — short branches, PR review, merge, deploy — and most of the complexity beyond that is process borrowed from a problem they don’t have.