Trunk-Based Development Doesn't Survive Microservices Without Feature Flags
Most engineering organizations adopt trunk-based development the same way: a senior engineer reads the DORA research, gets convinced that long-lived feature branches are the root cause of slow shipping, and announces that the team is going to commit to main from now on. For a single-service codebase with a small team, this works almost immediately. Lead times drop. Merge conflicts evaporate. People notice.
Then the same approach gets tried on a microservices architecture, and it falls apart in ways nobody quite anticipated. Not because trunk-based development is wrong for microservices — it isn't — but because the model assumes a primitive that most multi-service teams haven't actually built yet. The branching strategy is the visible part. The coordination layer underneath is the part that determines whether trunk-based development with feature flags for safety becomes the team's normal mode or quietly dies in the third sprint.
The coordination problem hiding inside the branching decision
A feature touching one service is easy. The engineer merges a small commit to main, CI runs, the change deploys, and the world keeps moving. Trunk-based development is genuinely better than feature branches for this case because the friction was always lower than the merge-conflict tax made it look.
A feature touching three services is a different problem. Service A needs to expose a new endpoint. Service B has to start calling it. Service C has to handle the new event shape produced as a side effect. The naive trunk-based path is: every engineer commits to main on their service whenever their piece is ready. CI deploys each service independently as commits land.
The naive path has a window. There's a period — sometimes minutes, sometimes days — during which Service B is calling an endpoint that doesn't exist on Service A yet, or Service C is consuming a stale event shape. In a feature-branch model, the branches stayed unmerged until everything was ready, and a coordinated release brought them all live together. The "long-lived branch" some people called wasteful was, in reality, a coordination mechanism in disguise. Removing it without replacing what it was actually doing leaves you with a coordination hole.
Most teams hit this hole, blame trunk-based development, and retreat to release branches. The mistake isn't trunk-based development. The mistake is failing to put a coordination primitive in place to do the job the long branch used to be doing.
Feature flags as the coordination contract
Feature flags, used correctly, are that primitive. Not as a "ramp this slowly to users" tool — that's the application-layer use case — but as a release contract that lets each service deploy independently while keeping the new behavior dormant until all services are ready to participate.
The pattern is straightforward but goes against how most teams think about flags. Service A deploys its new endpoint behind a flag check: the endpoint exists in the running binary but returns a "not yet" response when the flag is off. Service B deploys code that calls the new endpoint, also behind a flag — the call path is shipped but inert. Service C deploys handlers for the new event shape, gated the same way. Each service is in production with the new code. None of it is active.
The flag flip is the release. It activates the new behavior across all three services more or less atomically, because the flag evaluation is fast and the code paths are already loaded. If something goes wrong, the flag flips off and all three services revert simultaneously without any of them needing to redeploy.
This is what release coordination across multiple microservices teams actually requires: a layer where the activation decision is decoupled from the deployment decision, and where the activation can be coordinated cross-service without freezing any of the underlying services. The branching strategy doesn't give you this. CI/CD doesn't give you this. Only an external control plane — what a flag system is — gives you this.
The deploy frequency vs. deployment risk tradeoff is fake when this works
A common framing in engineering leadership conversations is the deploy frequency vs. deployment risk tradeoff for engineering leaders: ship more often and accept more risk, or ship less often and slow your team down. Most teams treat this as a real choice with a real Pareto frontier. It mostly isn't.
The frontier is an artifact of the implementation, not a law of physics. When deploying a change means activating it everywhere at once, frequent deploys do mean more risk per unit time. When deploying a change means putting inert code into production behind a flag, deploys carry almost no risk at all — the risk is concentrated entirely at the activation step, which is independently controlled. The team can deploy hourly with low risk and activate weekly with the existing review processes intact. Frequency and risk get unbundled.
The version of this idea most teams have absorbed is "decouple deploy from release for user-visible features." The version that actually matters for microservice architectures is broader: decouple every cross-service change. Even back-end-only changes that no user will ever see directly benefit from being deployable independently and activatable atomically. The flag isn't a UX tool. It's the contract between services that lets them ship on their own schedules.
Why this fails in practice anyway
Plenty of teams know all of this in principle and still end up with release branches across their microservices. The failure mode is usually one of two things, and both are real.
The first is flag proliferation across service boundaries. If every cross-service change needs a flag, you accumulate flags fast. Each one needs to be created, named consistently across services, ramped together, and cleaned up after activation. Without a system that treats these as related — that knows the Service A flag and the Service B flag are part of the same release — they become independent toggles that drift apart over time. The team ends up with the second codebase problem at distributed scale.
The second is that the flag system itself becomes a coordination bottleneck. If creating a flag requires opening a ticket with the platform team, or if cross-service flag changes need to be applied service by service through separate dashboards, the friction wipes out the gains. The whole argument depends on the flag being faster and cheaper to manipulate than a deploy. When that breaks down, teams correctly conclude that the overhead isn't worth it and go back to coordinating through branches.
Both of these failure modes are about the platform, not the strategy. Trunk-based development with feature flags for safety works when the flag layer is itself a first-class primitive — flags created automatically when a cross-service change is detected, named consistently across services by default, activated and reverted as logical units, and cleaned up without engineer attention once they've served their purpose. None of that is conceptually hard. All of it is implementation work most teams haven't done.
The platform problem that release coordination really is
The reframe worth making is that release coordination across microservices isn't a branching problem or a process problem. It's a platform problem. The teams that ship continuously across many services don't do it through better merge etiquette. They do it because their platform makes the inert-deploy-then-coordinated-activation pattern the default path, with the flag plumbing handled automatically rather than negotiated each time.
The deploy-frequency-versus-deployment-risk framing collapses once the platform makes coordinated activation cheap. The trunk-based development debate that consumes so much engineering leadership oxygen is, at its core, a debate about whether you have the coordination primitive in place. If you do, trunk-based is obvious. If you don't, it's irresponsible.
DeployRamp's bet is that the coordination layer should be invisible to the engineer writing the cross-service change. When the PR scanner detects a change that crosses a service boundary or modifies an API contract, the flag goes in automatically — consistently named across the services involved, activated as a single logical release, and cleaned up after the activation has been stable long enough to trust. The pattern stops being a discipline that requires someone to enforce it and starts being how the platform already works. That's the shift that makes trunk-based development actually durable in a microservices world — not better branching, but a release coordination primitive that costs nothing to use.