Back to articles
Production-level
18 min read

Trunk-Based Development vs Git Flow

A production-focused comparison of trunk-based development and Git Flow through release risk, team maturity, CI discipline, and rollback ownership.

release-engineeringgit-flowtrunk-based-developmentdevopsproductionci-cdarchitecture
João Gustavo Camilo JúniorEngineering Lead
Engineering

Trunk-Based Development vs Git Flow

The branch strategy is rarely the real problem.

The real problem is the release system hiding behind it.

Teams argue about trunk-based development and Git Flow as if the branch diagram itself decides delivery quality. It does not. The diagram only exposes how the organization pays for integration, review, stabilization, rollback, and ownership.

Trunk-based development pays that cost continuously.

Git Flow pays more of it at release boundaries.

Both models can work. Both can fail badly. The important question is not which model looks cleaner on a slide. The important question is where your team can tolerate risk, where your tooling is mature, and how quickly you can restore production when a change behaves differently under real traffic.

The Context That Usually Gets Ignored

Most teams do not choose a branching model from first principles.

They inherit one.

Some inherit Git Flow because an older backend platform needed explicit versions, release branches, and hotfix discipline. Some inherit a simplified trunk model because long-lived branches became a tax. Some adopt GitHub Flow because the pull request interface became the process.

The vocabulary then becomes imprecise.

One team says "trunk-based development" but keeps pull requests open for nine days. Another says "Git Flow" but only uses main, develop, and occasional release branches. Another says "continuous integration" but integrates once per sprint and runs a slow regression suite before every merge.

That matters because DORA's definition of trunk-based development is not "we have a branch named main." It is frequent integration into trunk, usually at least daily, with small batches and fast automated validation. DORA also connects the practice to continuous integration, not just repository structure.

Git Flow has a different origin. Vincent Driessen's original model defines long-lived master and develop branches, plus feature, release, and hotfix branches. In his 2020 reflection, he explicitly warned against treating the model as a universal standard, especially for continuously delivered web applications. He still considered it relevant for explicitly versioned software and products that must support multiple versions in the field.

That distinction is still useful.

If your system is a SaaS product deployed by the team that owns it, the pressure is usually lead time, rollback speed, and production readiness. If your system ships versions to customer-controlled environments, the pressure shifts toward release lines, support windows, patch trains, and traceability.

Those are different operating models.

They deserve different branching constraints.

The Problem Is Integration Timing

Branching strategy is an answer to one question:

When do we integrate risk?

Trunk-based development integrates risk early and often. The system discovers conflicts while the change is still small. The cost is daily mainline discipline: fast tests, low review latency, feature flags, small batches, and a habit of reverting bad changes.

Git Flow delays some integration risk until features reach develop, releases branch off, or hotfixes need to be merged back into multiple lines. The cost is extra branch choreography. The benefit is a clearer staging area for formal release preparation, explicit versioning, and supported production lines.

Neither model removes complexity.

It only moves it.

A branching model is not a delivery strategy. It is a pressure map. It shows where the team expects integration pain, release coordination, and rollback responsibility to happen.

Martin Fowler's branching patterns make this point indirectly: branch management is about integration frequency and the path from mainline to production. A release-ready mainline works well when the team integrates frequently and keeps the branch healthy. If the team integrates once a month, insisting on release-ready mainline can become theatre. The organization has not earned the simplicity yet.

That is the part many teams miss.

Trunk-based development is simple only after the team has built the engineering system that makes it simple.

Git Flow is structured only if the team respects the operational contract behind each branch.

What Trunk-Based Development Optimizes For

Trunk-based development optimizes for short feedback loops.

The main branch is the center of gravity. Developers work in small batches, integrate frequently, and rely on automated checks to keep the branch healthy. In small teams, some changes may go directly to trunk. In larger teams, short-lived pull requests or merge queues often act as the review and validation boundary.

The important point is branch lifetime.

A pull request that exists for a few hours can still be compatible with trunk-based development. A feature branch that survives for two weeks and becomes the private integration environment for a whole team is no longer trunk-based in any meaningful operational sense.

The model works when the team has:

  • Fast commit-level tests.
  • Clear ownership of broken builds.
  • Small-batch development habits.
  • Low review latency.
  • Feature flags or branch-by-abstraction for incomplete work.
  • Deployment automation.
  • Revert discipline.
  • Observability good enough to detect bad changes quickly.

DORA's guidance is direct here: teams practicing trunk-based development should keep few active branches, merge to trunk at least daily, and avoid code freezes and integration phases. This is not aesthetic advice. It is an attempt to reduce merge complexity and keep the system current.

The operational benefit is significant.

When the mainline is healthy, production candidates can be created at any time. A bad change is easier to identify because the batch size is smaller. Refactoring becomes less dangerous because developers do not have to reconcile weeks of divergent work. The team spends less time negotiating merge events and more time improving the system.

But the failure mode is also real.

Trunk-based development punishes weak CI.

If tests are slow, flaky, or incomplete, the main branch becomes a shared anxiety surface. If review takes days, developers batch larger changes and the model degrades. If flags are unmanaged, release safety becomes flag debt. If the team cannot revert quickly, every merge feels like deployment risk.

Trunk-based development without fast validation is not a high-performance practice. It is just shared instability with fewer places to hide.

The discipline is not the branch name.

The discipline is keeping every accepted change close enough to production quality that the repository does not need ritual stabilization.

What Git Flow Optimizes For

Git Flow optimizes for explicit release governance.

The model separates production state from active integration. main or master represents production-ready releases. develop represents the integration line for the next release. Feature branches merge into develop. Release branches prepare a version. Hotfix branches start from production and merge back into the relevant lines.

This is more ceremony than trunk-based development.

Sometimes that ceremony is useful.

It creates a language for teams that need to answer:

  • Which commit represents production?
  • Which version is being stabilized?
  • Which fixes went into a patch release?
  • Which features missed the release train?
  • Which supported version needs the hotfix?
  • Which release branch owns final verification?

For packaged software, mobile applications, customer-managed deployments, regulated systems, and enterprise products with support windows, this structure can be valuable. The release branch is a boundary around a support obligation.

Git's own documentation describes long-running branches as useful for progressive stability. A stable branch can contain only released or release-ready code, while another branch can receive work that is still moving toward stability. That pattern is not obsolete. It is just expensive when used for systems that do not need it.

The cost appears in merge paths.

Every hotfix must return to the future line. Every release branch can diverge. Every delayed merge increases semantic conflict, not just textual conflict. A test suite might pass on develop, pass on main, and still fail after a back-merge.

Git Flow also creates a tempting illusion: because main is clean, the release system is healthy.

That is not guaranteed.

If develop is unstable for long periods, production risk moved into an integration branch. If release branches require days of stabilization, the organization pays integration tax late. If hotfix back-merges are skipped, the next release can reintroduce the same defect.

Git Flow works when the team treats branches as operational contracts.

It fails when branches become dumping grounds for unfinished coordination.

The Deep Technical Difference

The technical difference is not "one branch versus many branches."

The technical difference is how each model manages divergence.

In trunk-based development, divergence is kept small. The team uses small changes, short-lived branches, flags, abstractions, and automated validation to keep the shared line healthy. The system trades branch isolation for integration frequency.

In Git Flow, divergence is explicit. develop, release/*, hotfix/*, and feature branches exist to represent different states of maturity or urgency. The system trades frequent mainline integration for clearer release stages.

That difference affects the entire delivery pipeline.

Review Latency

Trunk-based development needs review to behave like part of development, not like an external queue. A pull request that waits two days is not just delayed work. It is accumulating integration risk.

Git Flow tolerates longer feature review better, but only up to a point. A long-lived feature branch still creates stale assumptions. The merge to develop becomes the moment where hidden conflicts surface.

Test Strategy

Trunk-based development needs a fast commit suite. Slow tests can still exist, but they must be staged behind fast validation. If every merge waits on a brittle end-to-end suite, developers will batch work. Once they batch work, the model loses its advantage.

Git Flow can place heavier verification around release branches. That is useful when final release qualification requires manual checks, compliance review, upgrade testing, or customer-specific validation. The cost is slower feedback.

Feature Exposure

Trunk-based development usually separates deployment from release through feature flags, permission gates, branch-by-abstraction, or dark launches.

That is powerful, but it creates new operational debt. Flags need owners, expiry dates, default behavior, telemetry, and cleanup. The team must test both enabled and disabled paths when risk justifies it.

Git Flow can avoid some flag complexity by keeping incomplete work off the production line. That can be simpler for teams without mature flag infrastructure. It can also slow integration and make large features harder to validate incrementally.

Release Artifacts

In trunk-based development, the artifact should be created from a known mainline commit or from a short-lived release branch cut from mainline. The artifact, commit SHA, configuration, and deployment record must connect cleanly.

In Git Flow, release branches and tags carry more semantic weight. Version numbers, changelogs, release notes, and patch lines become part of the branch lifecycle.

Neither model excuses weak artifact traceability.

If production cannot be mapped back to source, configuration, migration state, and build inputs, branch policy is not the biggest problem.

Real-World Trade-Offs

The honest comparison is not "trunk is modern" and "Git Flow is old."

The honest comparison is operational fit.

Trunk-Based Development Works Best When

  • The product is continuously delivered.
  • The team can deploy or release frequently.
  • CI feedback is fast and trusted.
  • Code review latency is low.
  • The architecture supports incremental change.
  • Feature flags are governed.
  • Rollbacks or fix-forward paths are rehearsed.
  • The team owns production after merge.

The model is especially strong for SaaS systems where the team controls the runtime environment. If the deployment pipeline is mature, small changes reduce blast radius and shorten recovery.

Git Flow Works Best When

  • The product has explicit versions.
  • Multiple versions must be supported.
  • Releases require formal qualification.
  • Customers run different versions.
  • Hotfixes must target released lines.
  • Compliance or change management requires release boundaries.
  • Release notes, tags, and patch streams matter operationally.

The model is often reasonable for SDKs, desktop products, mobile apps, on-premise systems, embedded software, and enterprise platforms with contractual support windows.

Trunk-Based Development Fails When

  • Mainline breaks often.
  • Review queues are slow.
  • Tests do not catch meaningful regressions.
  • Feature flags become permanent branching in code.
  • Developers merge unfinished work without isolation.
  • Incidents cannot be tied back to small commits.

Git Flow Fails When

  • develop becomes a permanent unstable environment.
  • Release branches accumulate unrelated fixes.
  • Hotfixes are not merged back correctly.
  • Stabilization becomes a recurring phase.
  • Teams treat branch movement as a substitute for automated quality.
  • Merge conflict resolution happens under release pressure.

The trade-off is not only technical.

It is managerial.

Trunk-based development requires high trust in engineering feedback loops. Git Flow requires discipline around release ownership. Weak teams can misuse both. Strong teams can simplify either model.

Operational Considerations

A branch strategy should be evaluated through operations, not preference.

Deployment Cadence

If you deploy multiple times per day, long-lived release branches are usually drag. The branch model should support small changes moving quickly through validation.

If you release monthly to customer-controlled environments, a release branch may be the place where support obligations become visible.

Rollback Strategy

Trunk-based development needs small commits and clean deployment records. Rollback should not require detective work.

Git Flow needs reliable hotfix and back-merge discipline. The critical question is whether a production fix can be applied without losing the fix in the next release.

Database Changes

Branching models do not solve database compatibility.

Trunk-based development usually pushes teams toward expand-and-contract migrations, backward-compatible schema changes, and flags around behavior. Git Flow can defer incompatible changes until a release boundary, but long-lived branches can make migration sequencing harder if multiple branches modify the same schema.

The database is often where abstract branch debates become real.

Observability

Small changes are useful only if the team can see their effects.

For trunk-based development, every deployment should connect to metrics, logs, traces, error rates, and business signals. For Git Flow, release candidates and hotfixes need comparable visibility, especially when a patch changes behavior in a supported version that differs from develop.

Security and Compliance

Regulated environments sometimes prefer visible release boundaries. That does not automatically require Git Flow, but it does require evidence: approvals, traceability, artifact integrity, and reproducible builds.

Trunk-based development can satisfy those needs when the pipeline records the right facts. Git Flow can fail those needs when branch policy exists but artifacts are not reproducible.

AI-Assisted Development

AI agents increase the importance of small changes.

Generated code can be useful, but it can also produce confident edits across broad surfaces. Trunk-based development gives teams a strong incentive to constrain AI output into reviewable batches. Git Flow gives teams isolation, but that isolation can hide the scale of AI-generated divergence until merge time.

For AI-assisted work, the rule should be the same in both models:

  • Small commits.
  • Clear ownership.
  • Fast checks.
  • No unreviewed broad rewrites.
  • Revertable changes.
  • Prompt and agent configuration under version control.

DORA's version control guidance now explicitly treats version control as a safety net for AI-driven speed. That is the right framing. The faster code appears, the more disciplined the integration path must become.

A Production Story

Production Story

A SaaS team I worked with had a clean-looking Git Flow setup: main, develop, feature branches, release branches, and hotfix branches. On paper, production was protected. In practice, develop was broken most Thursdays, release branches lived for a week, and hotfixes were merged back only when someone remembered.

The incident started with a billing bug.

A small change in invoice proration went into a release branch during stabilization. The fix was correct for the release candidate, and the patch went to production. The team tagged the release, closed the incident, and moved on.

Two weeks later, the same bug reappeared.

Not because the fix was wrong.

Because the fix never made it back into develop.

The next release branch was cut from a line that still had the original bug. The team had treated the hotfix branch as emergency work instead of part of the permanent code history. The branching model had a place for the correction, but the operating discipline failed.

The fix was not "switch to trunk."

The fix was to make branch movement observable and enforceable:

  • Hotfix completion required a tag.
  • The fix had to be merged into every active release line.
  • CI verified the regression test on main, develop, and open release branches.
  • The release checklist included a back-merge verification step.
  • Ownership of the release branch moved from "whoever is available" to a named release captain.

Later, the team simplified the model. Most services moved closer to trunk-based development because they were SaaS services with frequent deployment. A few integration-heavy components kept release branches because customer upgrade windows made them useful.

That hybrid was better than the original purity.

It matched production reality.

Choosing Through Constraints

If you are deciding between trunk-based development and Git Flow, start with constraints.

Do not start with ideology.

Ask:

  • How often do we deploy?
  • How often do we release to users?
  • Who controls the runtime environment?
  • How many versions must we support?
  • How fast are our tests?
  • How long does review take?
  • Can we hide incomplete work safely?
  • Can we revert or fix forward quickly?
  • Can we trace production to source and artifact?
  • Can we handle database changes without locking the release?

If the answers show mature automation, frequent deployment, low review latency, and strong observability, trunk-based development is usually the simpler operational model.

If the answers show explicit version support, release qualification, customer-controlled environments, or patch lines, Git Flow or a simplified variant may be justified.

The mistake is carrying all of Git Flow into a SaaS product that deploys continuously.

The opposite mistake is forcing trunk-based development onto a product that must maintain multiple supported versions without investing in the tooling and release policy that make that safe.

A Practical Middle Ground

Many production teams do not need textbook Git Flow or pure trunk-based development.

They need a small set of rules that match their release risk.

For SaaS teams, I usually prefer:

  • main as the release-ready line.
  • Short-lived branches for review.
  • Merge queues for high-traffic repositories.
  • Feature flags for incomplete exposure.
  • Release branches only when stabilization or rollback requires them.
  • Tags for production deployments.
  • Fast revert paths.

For versioned products, I prefer:

  • main as the production or latest release line.
  • Explicit release branches for supported versions.
  • Short-lived feature branches.
  • Strict hotfix back-merge policy.
  • Automated tests on every supported release line.
  • Signed tags where integrity matters.
  • Clear end-of-support rules for old branches.

The common requirement is smaller than people think:

Keep the branch model understandable under incident pressure.

If the production incident commander cannot quickly answer which branch owns the fix, where it must be merged, what artifact must be built, and how to prove the fix is in the next release, the model is too vague.

Key Takeaways

Further Reading

The Decision Principle

Trunk-based development and Git Flow are not moral positions.

They are operating models.

Trunk-based development asks whether your team can keep integration continuous, mainline healthy, and production changes small. Git Flow asks whether your team can manage release lines, hotfixes, stabilization, and back-merges without losing traceability.

The better model is the one that makes production risk visible early enough for your team to act.

For many SaaS teams, that means trunk-based development with disciplined review, strong CI, feature flags, and deployment automation.

For products with versioned releases and support obligations, that may mean Git Flow or a smaller release-branch model.

The operating discipline behind it is what matters.