A Practical Git Branching Strategy
If you’ve worked in DevOps long enough, you know the pain: developers pushing features left and right, bugs slipping into staging, last-minute hotfixes after a release — and somehow, you’re expected to keep all environments stable.
I’ve been there.
After working on several projects with different teams, I finally settled on a Git branching strategy that actually works. It’s clean, scalable, and plays nicely with CI/CD pipelines.
In this post, I’ll walk you through the approach I personally use to manage code across environments like DEV, TEST, and PROD — while keeping my sanity intact. (Diagram included, of course! 👇)
The Branches I Work With
Here’s how I structure the repository:
master– Stable code. Whatever lands here gets deployed to TEST & PROD.develop– The working branch. Merges happen here and go to DEV.feature/*– For new features. Short-lived and focused.bugfix/*– For issues caught before the release.release/*– Prepares a version for production.hotfix/*– Urgent production fixes.
Each branch has a specific purpose, and this structure ensures clean separation between environments and responsibilities.
My Workflow in Action (Step by Step)
Let me break it down, just like I follow it day-to-day:
Get Avadhoot Dattatray Bharankar’s stories in your inbox
Join Medium for free to get updates from this writer.
1. Start from Stability
master → develop
I always branch develop from master to ensure I’m working with the latest stable production code.
2. Feature Development
develop → feature/* → develop
Each new feature gets its own branch. Once it’s done, tested, and reviewed, it’s merged back into develop.
This keeps the working branch clean and avoids messy merge conflicts.
3. Fixing Bugs Along the Way
develop → bugfix/* → merged into release + develop
Sometimes, bugs creep into the DEV environment or get spotted during testing. I fix them in a bugfix/* branch and make sure to merge the fix into both release and develop.
4. Cutting a Release
develop → release/*
Once a set of features is ready and tested in DEV, I create a release branch. This is the version that goes through UAT and final QA.
If any bugs are caught here, I fix them using bugfix/* and merge them back in.
5. Ready to Go Live
release → master → Tag → TEST/PROD
When QA signs off, I merge the release into master, tag the commit (yes, tagging is mandatory), and trigger deployments to TEST and then PROD.
6. Handling Production Issues
master → hotfix/* → master + develop
If something breaks in production (it happens!), I create a hotfix/* branch directly from master, fix it quickly, and merge it back into both master and develop.
This ensures the fix reaches production and doesn’t get lost when new features are deployed.
My Simple Deployment Rules
develop → DEV
master → TEST and PROD
Tagging is a must before pushing to TEST or PROD — this keeps things traceable and safe.
Why This Strategy Works (for Me)
Keeps master always clean and deployable
Lets developers work independently without stepping on each other
Supports parallel testing, bug fixing, and releasing
Makes hotfixes safe and easy to roll out
Works smoothly with GitLab or GitHub CI/CD pipelines
Final Thoughts
Now, with this model in place, I spend less time firefighting and more time improving automation, performance, and monitoring — the things DevOps engineers should be doing.
If you’re juggling multiple environments and want a clean way to manage them, try this approach. It might just save you (and your sleep cycle).




