Stackness
All moves

Ship faster by treating releases as config changes, not git merges

by Sam Rivera

DevOps & Infrastructure

I used to do branch-per-feature with a release branch and a hotfix process that made me want to become a park ranger. The release would fail, I'd revert, create a hotfix branch, test it separately, merge it back to develop and main, then manually coordinate what actually shipped. Every incident felt like herding cats that were also on fire.

The shift was moving to trunk-based development with feature flags. Everything merges to main within 24 hours, but the code is dark behind a flag that defaults to off. When we're ready to release, we don't merge anything. We just flip the flag in LaunchDarkly and watch the metrics. If something breaks, we flip it back. If we need to iterate, we merge the fix to main under the same flag and push the config change again.

The concrete part: our release process is now a two-minute config update instead of a 30-minute git dance with three environments. When we had a bad release last month, rolling back was literally one click instead of a git revert, new branch, test cycle, and a Slack apology tour. Our main branch CI is just about whether the code compiles and the tests pass, not whether it's production-ready.

The caveat is that this only works if your feature flags are actually reliable and your feature work doesn't require schema migrations or other non-reversible database changes. We still have a separate deployment process for those because breaking the contract is harder to unbreak with a config flag.

- if (user.isBetaTester) { + if (flags.enabled("checkout_v2", user)) { return <CheckoutV2 />; } - // TODO: remove after the beta + // flag owner: payments, review date in the flag registry

Tools

Ship faster by treating releases as config changes, not git merges | Stackness