Trunk-Based Development vs Git Flow
Trunk-based development merges small changes into main constantly; Git Flow routes work through long-lived branches. How each affects releases and CI.
Trunk-based development and Git Flow are two branching models that answer the same question differently: how should a team organize commits, branches, and merges on the way to a release? Trunk-based development keeps everyone merging small changes into a single shared branch continuously; Git Flow routes work through a structured set of long-lived branches — develop, feature, release, and hotfix — before anything reaches production.
Git Flow: a branch for every purpose
Git Flow, popularized in the late 2000s, defines a specific role for each branch:
main— always reflects what’s in production.develop— the integration branch where finished features accumulate between releases.feature/*branches — created offdevelopfor each new piece of work, merged back intodevelopwhen done.release/*branches — cut fromdevelopwhen preparing a release, used for final stabilization and version bumps, then merged into bothmainanddevelop.hotfix/*branches — cut directly frommainto patch production urgently, merged back into bothmainanddevelop.
This structure gives a team clear checkpoints: develop is where integration happens, release branches are where a version gets locked down and tested, and main never sees anything that hasn’t passed through that pipeline. It’s well suited to software with scheduled releases, multiple versions supported in parallel, or a formal QA sign-off stage between “feature complete” and “shipped.”
Trunk-based development: one branch, constant integration
Trunk-based development inverts the emphasis. There’s a single long-lived branch — usually still called main or trunk — and developers merge into it directly, or via very short-lived feature branches (often open for less than a day). The core discipline is that main is always kept in a releasable state, which means:
- Changes are broken into small, frequent commits rather than large, batched features.
- Incomplete work ships behind a feature flag rather than living on a long-lived branch, so
mainnever has to wait for a feature to be “done” before absorbing it. - CI runs on every merge to
main, and a red build is treated as a stop-the-line event, not something to fix “eventually” on a separate branch. - Releases are cut from
mainat any point, often via tags, rather than being staged on a dedicated release branch.
This model trades the structure of dedicated integration and release branches for a tighter feedback loop: merge conflicts are smaller and more frequent instead of large and rare, and integration problems surface within hours instead of at the end of a sprint.
Comparing the two
| Git Flow | Trunk-based development | |
|---|---|---|
| Long-lived branches | main, develop, plus release/hotfix branches | Just main/trunk |
| Feature branch lifespan | Days to weeks | Hours to a day, or none |
| Incomplete work | Stays on its branch until done | Merged behind a feature flag |
| Release mechanism | Dedicated release/* branch | Tag or cut directly from main |
| Merge conflict size | Larger, less frequent | Smaller, more frequent |
| Best fit | Scheduled releases, multiple supported versions | Continuous deployment, single production version |
Why trunk-based development pairs with CI/CD
Trunk-based development and continuous deployment reinforce each other. If every merge to main has to be release-ready, you need automated tests catching regressions immediately — there’s no release-branch stabilization period to catch problems later. And if main is always deployable, there’s little reason to delay deploying it, which is exactly the premise of continuous deployment. Teams practicing full continuous deployment almost always use some form of trunk-based development, because a Git Flow-style pipeline with dedicated release branches assumes there’s a batching and staging step that continuous deployment is specifically designed to eliminate.
The reverse pairing holds too: Git Flow tends to show up alongside more traditional CI/CD pipelines where releases are versioned, scheduled, and sometimes supported in parallel — think desktop software, libraries with semantic-versioned releases, or systems where a customer might stay on an older major version for a while.
Feature flags as the load-bearing piece
The part of trunk-based development that trips teams up most often isn’t the branching — it’s what replaces the isolation a feature branch used to provide. Without a long-lived branch to hide unfinished work, teams need feature flags to merge incomplete code into main without exposing it to users. This shifts complexity from Git history into application code: instead of a merge conflict resolving whether a feature exists, a runtime flag decides whether it’s active. That’s a reasonable trade for faster integration, but it does mean flag cleanup has to become a real habit — stale, forgotten flags accumulate as technical debt in exactly the way stale feature branches used to.
Which one fits your team
Neither model is universally correct. Git Flow’s structure earns its keep when a project needs to support multiple released versions simultaneously, when releases go through a distinct QA or compliance gate, or when contributors work asynchronously across time zones without shared CI infrastructure. Trunk-based development earns its keep when the team ships continuously to one production environment, has strong automated test coverage, and would rather resolve small conflicts constantly than large ones occasionally. Many teams also land somewhere in between — a simplified Git Flow with just main and short feature branches, skipping develop and release branches entirely, borrowing trunk-based development’s cadence without going all the way to flag-gated continuous deployment.
The takeaway
Git Flow organizes work through dedicated branches for integration, release, and hotfixes, giving teams clear checkpoints at the cost of larger, less frequent merges. Trunk-based development collapses that structure into a single branch kept perpetually releasable, trading branch isolation for feature flags and constant CI feedback. The right choice tracks how a team actually ships: scheduled, versioned releases favor Git Flow’s structure, while continuous deployment to one production environment favors trunk-based development’s tighter loop.
Keep reading
The Lycoris Team · · 4 min read Git Hooks Explained: pre-commit, pre-push, and More
Git hooks are scripts that run automatically at points in the Git workflow, like before a commit or push. How they work and when to use them.
The Lycoris Team · · 5 min read Git Rebase vs. Merge: A Practical Guide
When should you rebase and when should you merge? A clear, example-driven breakdown of the trade-offs, plus a simple workflow you can adopt today.
Chisato · · 5 min read What Are Reproducible Builds? Verifiable Software, Explained
Reproducible builds produce bit-for-bit identical output from the same source, so anyone can verify a binary. Why they matter and how to achieve them.