Trunk-based development is a source-control workflow where every engineer integrates small changes into a single shared branch - the trunk - at least once a day. Branches are short-lived, the trunk is always releasable, and unfinished work hides behind feature flags so deploys decouple from releases.
How does trunk-based development work?
Trunk-based development (TBD) is a branching model where the team agrees that the trunk - usually main - is the integration point, and everyone keeps in step with it on a sub-day cadence. The mechanics fall out of that one rule.
- An engineer pulls
main, opens a short-lived branch (locally, or pushed to the remote for review), and starts a small change. The change is sized to land in hours, not days - a single behaviour, a single refactor, a single bug fix. - The change is opened as a pull request that runs the CI suite on every push. A short, automated check-plus-review loop is the only gate between the change and the trunk.
- Once CI passes and review approves, the change is squash-merged into
mainand the branch is deleted. Nothing long-lived survives. - The trunk stays in a releasable state. Any commit on
maincan in principle be built and shipped, even if some features behind feature flags are not yet complete.
Two pieces are non-negotiable for the model to work. First, CI on every commit: a unit-plus-integration suite that runs in minutes and gives a single green/red signal per change. Without it the trunk drifts red and the model collapses under its own weight. Second, feature flags for any work that takes longer than a day to finish; flagged code rides the trunk to production with the flag off, instead of waiting in a long-lived branch.
There is a continuum. Pure TBD ("commit straight to main, no PR") works for small co-located teams that pair on everything. Scoped-branch TBD - short-lived branches of hours to a day, PR-reviewed, squash-merged - is what most modern teams actually run, and is the shape recommended by the original Continuous Delivery literature.
Why does trunk-based development matter?
The reason teams adopt TBD is that branch lifetime is the single biggest predictor of integration pain. Two-week feature branches accumulate merge conflicts, drift away from main, hide untested code from CI, and produce the most expensive merges of the cycle. Daily integration pays that cost in small change while the context is still fresh, instead of letting it compound until the merge becomes a project.
The other reason is throughput. Year after year the DORA State of DevOps report finds that elite performers - teams shipping multiple times a day with lead times under an hour - cluster around the same set of practices: low branch counts, trunk-based development, strong CI. The mechanism is not magic; small batches are easier to reason about, easier to test and easier to roll back than large ones, and TBD forces batches to stay small.
Trade-offs worth naming up front:
- CI investment is mandatory. A flaky or slow CI suite turns TBD into a permanent traffic jam at the merge button. Teams typically need run times under ~10 minutes and flake rates well under 1% before TBD stops hurting.
- Code review compresses. Two-week feature branches let reviewers read a polished story; TBD reviews are small slices that each look unfinished without context. Teams compensate with pair programming, async design docs, or RFC-style review of the design separate from the code.
- Feature flags become load-bearing. Without them, "always-releasable trunk" means "no work in progress on trunk", which is unworkable. The flag system becomes part of the deployment surface, with its own observability and cleanup discipline.
The honest summary is that TBD is not a free productivity boost. It is a discipline that pays for itself only after CI and flag tooling are healthy. Teams that adopt the branching change without those foundations get the cost without the benefit and then blame the model.
Trunk-based development vs GitFlow vs GitHub Flow
These three are the most-confused branching models, and the differences are real.
- GitFlow uses parallel long-lived branches (
develop,release/*,hotfix/*,feature/*) with explicit merge windows. It was designed for versioned, downloadable software that ships on a release calendar - desktop apps, libraries, embedded firmware. The model is precise and traceable, and miserable for continuous web delivery because the branches age and the merges balloon. - GitHub Flow is a single long-lived branch (
main) plus short-lived feature branches that PR back into it. It looks like TBD, and for small teams it is - but it has no opinion on branch lifetime, so teams routinely run "feature" branches for weeks under the GitHub-Flow name. The discipline is in the conventions, not the model. - Trunk-based development is GitHub Flow with the explicit rule that branches die in under a day or two, and the corollary that unfinished work uses feature flags rather than branches. Those two extra constraints are what differentiate TBD from generic short-lived-branch workflows.
In practice many modern teams that say "we use GitHub Flow" are actually trying to do TBD without naming it, and the bottlenecks they hit - long-lived release branches, multi-day code reviews, drifty integration tests - are the symptoms of not committing fully to the lifetime and feature-flag constraints above.
How do popular CI/CD tools support trunk-based development?
Any modern CI/CD platform can run trunk-based development - the question is how much extra glue the workflow needs around the merge.
- GitHub (Actions + branch protection + merge queues) is the most polished end-to-end story for TBD on a major hosted platform: required status checks, auto-rebase merge queues that serialise commits onto
mainwhile CI runs, and squash-merge defaults. If your code already lives on GitHub, you get most of the workflow shape for free. - GitLab CI/CD with merge trains is arguably the strongest mature implementation for high-velocity teams: changes are queued, CI runs against the projected post-merge state, and the merges land in order without one bad commit breaking everyone else's run. If you operate at the scale where merge conflicts on
mainare a real bottleneck, GitLab's merge trains are the better fit - that is a specific feature Buddy and most peers don't match, and you should pick the tool that has it. - Bitbucket Pipelines plus Bitbucket's merge checks cover the same shape for Atlassian shops; the integration with Jira is the differentiator.
- Jenkins can run a perfectly good TBD pipeline, but the team writes the gates, the auto-merge logic and the branch-protection enforcement themselves - maximum flexibility, maximum glue.
- CircleCI and Travis focus on the CI half; the branching discipline and merge mechanics live in the SCM provider.
- Argo CD and other GitOps tools sit downstream of the merge - once
mainmoves they reconcile the cluster. They don't care which branching model produced the commit, but they reward TBD because every reconciliation is small. - Buddy is one good option when the team wants the post-merge half of TBD - build, test, deploy, health-check - collapsed into a single pipeline file that triggers on every push to
main. Buddy pipelines are wired around the "every commit on the trunk is a deploy candidate" assumption: anON_EVERY_PUSHtrigger filtered torefs/heads/main, conditional actions for sandbox previews per branch, and direct sandbox/distribution updates so the deploy step does not need a second tool. It will not replace GitHub's branch protection or GitLab's merge trains - those are SCM-side concerns - but for everything that happens after the squash-merge, it keeps the pipeline boring and visible.
The honest summary: TBD is mostly an SCM-and-discipline question, and the major Git hosts (GitHub, GitLab, Bitbucket) own the merge half. The CI/CD platform's job is to make the post-merge half - build, test, deploy, observe - reliable and short. Buddy does that part well; for the merge-queue and merge-train side, stay with your SCM.
Example
The pipeline below is the post-merge half of a trunk-based workflow: it triggers on every push to main, runs the full test suite, builds and publishes an artifact, deploys to a sandbox preview, runs an HTTP smoke check, and only promotes to the production distribution if every step succeeds.
# .buddy/buddy.yml - every push to main is a deploy candidate
- pipeline: "trunk-deploy"
trigger: "ON_EVERY_PUSH"
refs:
- "refs/heads/main"
actions:
- action: "Test & build"
type: "BUILD"
docker_image_name: "node"
docker_image_tag: "20"
execute_commands:
- "npm ci"
- "npm test -- --ci"
- "npm run build"
- action: "Publish versioned artifact"
type: "BUDDY_CLI"
execute_commands:
- "bdy artifact publish web:$BUDDY_EXECUTION_REVISION ./dist --create"
- action: "Deploy to preview sandbox"
type: "BUDDY_CLI"
execute_commands:
- "bdy sandbox update preview --env BUILD=$BUDDY_EXECUTION_REVISION"
- "bdy sandbox restart preview"
- action: "Smoke check the preview"
type: "HTTP_REQUEST"
url: "https://preview.example.com/healthz"
expected_status_code: 200
retries: 10
retry_delay: 10
- action: "Promote to production"
type: "BUDDY_CLI"
run_when: "ON_SUCCESS"
execute_commands:
- "bdy distro route update prod-distro --domain=example.com --target=artifact=web:$BUDDY_EXECUTION_REVISION"
Every merge to main is one execution of this pipeline. Anything that fails - a flaky test, a broken build, a failing smoke check - stops the run before the production route changes, and the previous artifact keeps serving traffic. That is the entire safety story behind "the trunk is always releasable": not that the code on main is perfect, but that the pipeline between the trunk and the user catches the mistakes that aren't. For features that aren't yet ready to be visible, the same merge lands them on production with a feature flag off - released to the codebase, not to users - and a later flip, possibly gated by a canary release, exposes them.
Frequently asked questions
How is trunk-based development different from GitFlow?
GitFlow uses parallel long-lived branches - `develop`, `release/*`, `hotfix/*`, `feature/*` - with explicit merge windows. Trunk-based development collapses all of that into one main branch where branches live for hours and integrate continuously. The trade-off is that TBD requires strong CI and feature flags to compensate for the missing branch isolation; GitFlow gives you that isolation for free, at the cost of large, painful merges.
Do you still use pull requests in trunk-based development?
Yes - most teams keep "scoped branches" of a few hours to a day or two that go through a pull request for review and an automated check pass, then squash-merge to main. The defining property of TBD is the branch's lifetime (under a day or two) and the requirement that the trunk stays releasable - not whether a PR exists.
How small should a commit to trunk be?
Small enough that the trunk stays releasable after each merge. In practice that means each change is sliced so that it compiles, passes tests and is value-neutral on its own. Larger features land as a series of such slices, hidden behind a feature flag, and are turned on only after every slice is in.
Does trunk-based development work without strong CI?
No - that is the whole bargain. Without an automated test suite that runs on every merge in minutes, "everyone commits to main daily" becomes "everyone breaks main daily" and the model collapses. Teams without reliable CI should fix CI first and adopt TBD second; the order matters.
Suggest a new word or an edit to an existing one. Every submission is reviewed before it goes live.