Escaping Staging Hell: A Guide to the Flexible Release Branch Workflow

You just inherited a new project. The last developers have vanished. You’re facing down a staging
branch with over 1,200 commits that aren't in main
, and more than 400 open feature branches. You’ve been asked to deploy one specific, tested feature to production, but you have no idea which of the million commits to pick.
This isn’t a hypothetical scenario; it's the reality for too many development teams. This is staging hell, and it’s caused by a broken, undisciplined Git workflow.
The good news is that there’s a way out. It’s a pragmatic, flexible workflow that balances the need for speed with the critical requirement for stability, especially for teams that can't afford to spin up a new test environment for every single pull request.
The Core Problem: A Permanent Staging Branch is an Anti-Pattern
The most common source of chaos is treating staging
as a long-lived, permanent branch where features go to be tested. This inevitably leads to disaster:
- Untested Code Piles Up: Features are merged into
staging
, but for various reasons (bugs, changing priorities), they are never merged intomain
. Thestaging
branch slowly drifts into a wasteland of half-tested, un-deployable code. - Deployment Paralysis: You can't promote
staging
tomain
because it's full of unapproved features. You can't deploy anything without painstakingly cherry-picking commits, which is risky and error-prone. - Loss of Confidence: No one trusts what is on the staging server. Testing becomes meaningless because the environment doesn't reflect what will actually go to production.
The solution is to stop thinking of staging as a permanent place and start thinking of it as a temporary verification step.
The Flexible Release Branch Workflow
This workflow is designed for teams of any size who need to deploy rapidly while using a shared staging environment. It’s built on a simple principle: your main
branch is always stable, and everything is tested in a temporary, disposable branch before it gets there.
The Branches:
main
: The single source of truth. This branch always reflects what is in production. It must always be stable and deployable.feature/*
: Short-lived branches created for a single feature or bug fix (e.g.,feature/add-user-profile
).release/*
: A temporary branch used to batch features together for testing on the staging server (e.g.,release/2025-08-12
). This is the integration point.
The Step-by-Step Process:
-
Start from
main
: A developer always starts new work by branching from the latestmain
.git checkout main && git pull git checkout -b feature/new-cool-feature
-
Open a PR to a
release
Branch: When a feature is ready for QA, the developer opens a Pull Request. But here's the key: the PR targets the currentrelease
branch (e.g.,release/2025-08-13
). If one doesn't exist, the team creates it frommain
.- This is crucial: If there are merge conflicts, the developer who wrote the code is responsible for fixing them in their own PR. This avoids the "release manager" bottleneck.
-
Test the
release
Branch: Once arelease
branch has collected the features for the upcoming deployment and the staging server is available (see "Managing the Shared Staging Server: The 'Staging Lock'" section below), it is deployed. The team can now test all the new features and how they interact with each other in a clean, production-like environment. -
Promote the Release to
main
: Once therelease
branch is fully tested and approved by QA, a final PR is opened to merge therelease
branch intomain
.- Important: This PR should be merged with a regular, non-squash merge. Using the
--no-ff
(no fast-forward) flag is ideal as it creates a "merge commit" in yourmain
history, giving you a clear audit trail of when the entire release was promoted. Squashing here would lump unrelated features into one massive, unreadable commit. - This merge to
main
triggers the final deployment to production.
- Important: This PR should be merged with a regular, non-squash merge. Using the
-
Clean Up: The temporary
release
branch has served its purpose and can now be deleted. Your staging server is now free, ready for the next release cycle.
Managing the Shared Staging Server: The "Staging Lock"
A key logistical challenge arises with this workflow when you have multiple teams or release
branches ready for testing but only one staging server. You must coordinate to avoid deploying over each other.
The most effective solution for most teams is a social contract, or a "staging lock." The staging environment is a shared resource that must be "claimed" before it's used.
How it works:
- Declare Your Intent: Before deploying, a developer announces their plan in a dedicated, public place. This could be a
#deployments
Slack channel, a physical whiteboard, or a simple wiki page.- Example Message: "I'm deploying
release/team-alpha-sprint-23
to staging for QA. I expect to be done by 3 PM."
- Example Message: "I'm deploying
- Deploy and Test: Once the lock is claimed, that team can deploy their
release
branch and conduct their testing. - Release the Lock: When testing is complete and the
release
branch is either merged or no longer needed on staging, the team announces that the server is free.- Example Message: "
release/team-alpha-sprint-23
testing is complete. Staging is now free for the next person."
- Example Message: "
This simple communication process prevents chaos and ensures that what's being tested is what everyone thinks is being tested.
Flexibility and Scalability
Handling Hotfixes and Simple Changes
What if you have a critical bug or a simple typo fix? The workflow adapts.
- Hotfixes: For urgent fixes, branch directly from
main
, fix the code, and open a PR directly back tomain
. This bypasses the release process for speed. Once it's deployed, remember to mergemain
back into any activerelease
branches to keep them up-to-date. - Simple Changes: If a feature has thorough unit tests and doesn't require manual QA on a live server, you can agree as a team to merge it directly into
main
.
This workflow doesn't force ceremony for the sake of it. It provides a safety gate when you need one and gets out of the way when you don't.
Scaling Your Team
This pattern scales effectively as your team grows from 3 developers to 30 or more. The core principles remain the same, but the application adapts:
- Small Teams (3-5): A single
release
branch every few days or weekly works perfectly. - Medium Teams (30+): To avoid bottlenecks, you can run multiple, parallel
release
branches, perhaps one for each sub-team or major initiative.
How This Compares to Other Workflows
This pragmatic approach is superior for teams with shared infrastructure. Here’s why:
Flexible Release Branch
- Complexity: Medium-Low
- Release Cadence: Flexible (Daily, weekly, etc.)
- Best For...: Small to medium teams needing a shared staging environment.
- Pros: Balances speed and safety - Perfect for shared staging servers - Flexible: allows direct-to-main PRs for simple fixes.
- Cons: More overhead than direct-to-main models.
GitFlow
- Complexity: High
- Release Cadence: Slow & Planned
- Best For...: Large projects with explicit versioning (e.g., mobile or desktop apps).
- Pros: Very structured - Excellent for managing multiple versions.
- Cons: Overly complex for most web projects -
develop
branch can be a bottleneck.
GitHub Flow
- Complexity: Low
- Release Cadence: Continuous
- Best For...: Web apps/services that can automatically create a test environment for every PR.
- Pros: Very simple - Extremely fast cycle time - Minimizes merge hell.
- Cons: Not suited for teams that lack per-PR test environments.
Trunk-Based Development (TBD)
- Complexity: Very Low
- Release Cadence: Continuous
- Best For...: Elite CI/CD teams that rely heavily on feature flags and automated testing.
- Pros: Simplest model - Fastest possible path to production.
- Cons: "Breaks" on
main
can happen - Requires mature testing and feature flags.
This workflow is a pragmatic implementation of Continuous Integration and Continuous Deployment (CI/CD). You are integrating frequently, automating what you can, and using a reliable, repeatable process to deploy code. It gives you the speed of modern workflows without sacrificing the stability required in the real world of limited resources.