Git Ship is Git Flow without the develop branch. Every release gets its own release/x.y branch cut from main, features and hotfixes branch off that release, and shipping means merging the release into main, tagging it and deploying the tag. Code only ever moves in one direction.
Git Flow looks like structure. In practice it's a merge treadmill, and I ran on it for years.
After spending years using Git and Git Flow, I started noticing that I was spending an inordinate amount of time trying to manage and merge branches, rather than actually shipping code.
Don't get me wrong -- Git Flow inspired me to better manage my projects with Git. When I first discovered it, I loved how it brought with it some structure and organization to our development processes. But after using it for a few years, I started realizing that it also contained a lot of unneeded complexity.
While recording a screencast about Docker and git workflows many years ago, I found myself explaining the same complicated branching patterns over and over. And then it hit me -- there had to be a simpler way to maintain a stable codebase, while still keeping a focus on shipping features.
That's what led me to develop a new streamlined workflow that I named Git Ship that keeps the best parts of Git Flow, while also eliminating all of its unneeded complexity. The aim was to get code into production and eliminate the red tape so you can ship more code, faster.
The problem with Git Flow
If you've worked on any team-based project, you've probably used Git Flow. It's been the go-to branching strategy for many development teams over the past decade, and for good reason -- it brought much-needed structure to the development process.
Here's what a typical Git Flow workflow looks like:
When looking at this diagram, you might notice something that took me years to realize: there's merging happening everywhere.
You need to:
- Merge feature branches into
develop - Merge develop into
releasebranches - Merge
releasebranches back intodevelop - Create separate pull requests for hotfixes into both
developandmaster
And here's where things get really interesting (and by interesting, I mean complicated): hotfixes need separate pull requests for both develop and master branches.
According to Git Flow, these fixes never actually make their way into release branches. This opens up a loophole for critical fixes to potentially get lost in the process.
There is also so much merging going on! It can be tough to remember all of the naming conventions involved, and is just overly complex. This can also cause developers who are new to this process to become overwhelmed and frustrated, and cause slowdowns with your team actually shipping real code to production.
Questioning the status quo
After working with this workflow for years, I started questioning everything about this process:
- Why is the
master/mainbranch so underutilized? - Why is the
developbranch carrying so much weight? - Why do hotfixes need to use a completely different workflow than regular features?
- Why am I spending so much time merging branches instead of shipping code?
While feature branches made perfect sense, the rest of the workflow felt overly complex. I knew there had to be a way to maintain the benefits of a structured workflow, while also reducing the complexity of managing all of these branches.
This realization led me to develop a much simpler approach that focuses on what really matters: getting quality code into production.
Introducing Git Ship
After almost a year of experimentation and refinement, I developed a simplified version of Git Flow that I call "Git Ship". The name comes from its core focus: efficiently shipping code to production.
Here's what the Git Ship workflow looks like:
The first thing you'll probably notice is how much cleaner this looks.
The most notable change? The complete removal of the develop branch.
Why remove develop? Well, I realized that having a dedicated "development" branch doesn't actually help us ship code faster -- it just adds an extra layer of complexity to the mix.
Instead, Git Ship introduces a new concept that I like to call the "moving working tree". This is simply the release branch that you are currently working on.
Git Ship core concepts
Let's break down the core concepts that make Git Ship work:
- Everything starts and ends with the
mainbranch, which only ever contains production-ready code - Each new release gets its own branch created from
main(likerelease/1.1) - Your staging environment points to the current active release branch
- Feature branches and hotfixes branch directly from the release branch they belong to
- When code is ready to ship, the active
releasebranch gets merged intomainand then tagged - Production deployments happen directly from these tags
You keep Git Flow's release discipline and drop develop completely. With this approach, your code is always moving forward up to the next branch.
There's never any question about where code should go, or which branch code should be merged into.
Rather than juggling multiple long-running branches, you're creating a clear path for your code to flow from development to production. Each release branch represents a specific version of the software that you're actively working on, and once it's ready, it becomes part of your production code on the main branch.
How Git Ship works
Let's walk through the practical implementation of Git Ship. I'll break this down into the common scenarios you'll encounter when using this workflow.
Starting a new release
Every new body of work starts with creating a release branch from main:
git checkout -b release/1.1 mainThis release branch becomes your "moving working tree" -- the central point for all new development work for this release.
Working on features
Need to work on a new feature? Branch directly from your current release branch:
git checkout -b feature/new-homepage release/1.1After completing your work, create a pull request to merge your feature back into the release branch. This keeps everything organized and flowing in one direction.
Deploying to staging
Your staging environment should automatically track your current release branch. This means every merged feature automatically makes its way to staging for testing.
Shipping to production
When your release is ready for production, the process is straightforward:
- Merge your release branch into
main - Create a tag for the release (like
v1.1.0) - Deploy the tagged version to production
git checkout main
git merge release/1.1
git tag v1.1.0
git push origin main --tagsPush main and the tag together. git push origin --tags on its own only pushes tags, so the remote main would never get the merge.
Your deployment scripts should listen for new tags to be pushed upstream, and then automatically deploy that tag to production.
Handling hotfixes
Hotfixes are an area where Git Ship really shines compared to Git Flow. Hotfixes work exactly like regular features -- they just branch from the specific release that needs the fix.
For example, if you need to fix a bug in release/1.1, create a branch for it just like a regular feature branch:
git checkout -b feature/critical-fix release/1.1After fixing the issue:
- Create a pull request to merge into the
release/1.1branch - Once merged, merge
release/1.1intomain - Create a new tag (like
v1.1.1) onmain, and pushmainalong with the tag - Deploy the new tag to production
- Merge
mainback into the current release branch (likerelease/1.2) to bring the fix forward to your most recent release
git checkout main
git merge release/1.1
git tag v1.1.1
git push origin main --tags
git checkout release/1.2
git merge main
git pushBecause production deploys from tags on main, the merge into main has to happen before the tag. Otherwise the tag points at a commit that main doesn't contain.
I name hotfix branches feature/ on purpose, since they go through the exact same flow. If your team prefers a hotfix/ prefix so they stand out in the branch list, that works just as well.
Starting the next release
After shipping a release, immediately create your next release branch from the main branch, and then push it upstream:
git checkout -b release/2.0 main
git pushFYI you can set up
autoSetupRemoteto automatically push the new branch upstream withgit pushafter you create it. See this video for instructions on how to do this.
This keeps your workflow always moving forward, and gives you a clean slate for your next sprint of work to begin from.
Code always takes a clear, forward-moving path. You're always working toward the next release, and your code always flows in one direction: → production.
When to use Git Ship (and when not to)
Let's talk about where Git Ship really shines, and where you might want to consider a different approach.
Perfect for small teams
Git Ship works best when you have a small, tight-knit team of developers (typically less than 10), though it could also work for larger teams, as long as you have a clearly defined release cycle.
Since it relies on clear communication and coordinated releases, this typically becomes harder to manage as team size grows.
The workflow particularly excels when:
- You typically work in two-week sprints
- You ship code on regular release cycles
- You have a dedicated staging environment
- You need a structured but simplified approach to releases
- You want to eliminate unneeded complexity and focus on shipping code
Plays well with agile
If you're running sprints in an agile-like environment, Git Ship aligns perfectly with your workflow. Each release branch can correspond to a sprint, making it easy to:
- Track what features are going into each release
- Ship predictably at the end of each sprint
- Maintain a clear test workflow on staging
Git Ship vs trunk-based development
I know all of the CI/CD developers are coming up with all sorts of reasons why this wouldn't work 😅. That's because Git Ship isn't designed for continuous integration/continuous deployment environments.
If you're pushing multiple releases to production daily, you'll probably want to stick with a simpler trunk-based development approach to allow your team to ship code even faster.
However, if you are not shipping code multiple times per day to production and/or are currently using Git Flow, you may want to consider moving forward with evaluating Git Ship, as it could potentially be a vastly better choice for your project than your current workflow.
Setting up your tools
To make Git Ship work smoothly, you'll want to configure your deployment tools to:
- Automatically build and deploy any
release/*branch to staging - Trigger production deployments when new tags are pushed upstream
- Point your repository's default branch to your current release branch (and get in the habit of updating it at the start of each new release or sprint cycle)
The goal here isn't to create the most sophisticated branching strategy -- it's to help small teams ship code efficiently and reliably.
If your team needs something more complex, that's perfectly fine! You will always want to use what works best for you and your team. You shouldn't be dogmatic and expect Git Ship to work for every scenario.
Wrapping up: simplify your Git workflow
After using Git Ship for quite a while now, I can confidently say that it has drastically helped me and many of the teams that I've worked with in the past to focus more on what matters most -- shipping quality code to production -- and less on managing and maintaining complex git branching structures.
Teams I've moved to it tell me the same thing: fewer merge conflicts, and no more "which branch does this go in?"
Let's recap the key benefits:
- One main production branch (
main) - Clear, predictable release process
- Simplified hotfix workflow
- Less time spent managing merges
- Perfect alignment with sprint cycles
- Easier onboarding for new team members
And the best part? You don't need to make a dramatic switch all at once.
I recommend gradually transitioning from Git Flow to Git Ship on your next release cycle. Start by removing the develop branch and implementing the release branch workflow. Then, adapt the hotfix process as needed.
Or, go all-in and experiment with it on a new or incoming project, and make sure it works for you and your team before rolling it out company-wide.
Want to try Git Ship?
Here's how to get started:
- Create a new release branch from your
mainbranch - Point your staging environment to this
releasebranch - Start creating
featurebranches from yourreleasebranch - When ready to ship, merge to
mainand tag it! - Create your next
releasebranch and keep moving forward
I'd love to hear about your experiences if you decide to give Git Ship a try. The needs of every team are different, and your feedback could help improve this workflow even further.
Tell me how it goes on LinkedIn. I'm always up for talking through better ways to help teams ship code ✌️
If you're shipping Magento core fixes between releases, applying a GitHub commit as a Composer patch fits right into a release branch.



