Git Ship - A Simpler Flow to Release Code to Production

Git Ship - A Simpler Flow to Release Code to Production

Learn how to streamline your git workflow with Git Ship, a simplified approach to managing branches and releases. Spend less time managing branches and more time shipping code.

Are you familiar with that feeling of trying to merge branches in Git... only to wind up completely confused when trying to deal with the endless numbers of branches that exist?

I've been there.

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 it's unneeded complexity. The aim was to get code into production and eliminate the red tape so you can ship more code, faster.

If you've ever felt overwhelmed by complex branching strategies, or wished for a more straightforward way to manage your releases, this article is for you. Let's dive into why we need a simpler approach, and how Git Ship can help you spend less time managing branches and more time shipping great code.

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:

Git Flow diagram

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 release branches
  • Merge release branches back into develop
  • Create separate pull requests for hotfixes into both develop and master

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/main branch so underutilized?
  • Why is the develop branch 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:

Git Shipo diagram

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:

  1. Everything starts and ends with the main branch, which only ever contains production-ready code
  2. Each new release gets its own branch created from main (like release/1.1)
  3. Your staging environment points to the current active release branch
  4. Feature branches and hotfixes branch directly from the release branch they belong to
  5. When code is ready to ship, the active release branch gets merged into main and then tagged
  6. Production deployments happen directly from these tags

The beauty of this approach is that it maintains all the structure and safety of Git Flow, but with significantly less overhead. 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 main

This 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.1

After 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:

  1. Merge your release branch into main
  2. Create a tag for the release (like v1.1.0)
  3. Deploy the tagged version to production
git checkout main
git merge release/1.1
git tag v1.1.0
git push origin --tags

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.1

After fixing the issue:

  1. Create a pull request to merge into the release/1.1 branch
  2. Once merged, create a new tag (like v1.1.1)
  3. Deploy the new tag to production
  4. Merge main back into the current release branch to bring the fix forward to your most recent release.

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 push

FYI you can set up autoSetupRemote to automatically push the new branch upstream with git push after 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.

The beauty of this approach is that it maintains a clear, forward-moving path for your code, while also eliminating unnecessary complexity. 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

Not Ideal for Continuous Deployment

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.

I've heard from numerous code leads and teams that have adopted Git Ship, and they've all mentioned to me that they've seen a significant increase in productivity from their team, and a decrease in time spent managing development workflows.

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 recommmend 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:

  1. Create a new release branch from your main branch
  2. Point your staging environment to this release branch
  3. Start creating feature branches from your release branch
  4. When ready to ship, merge to main and tag it!
  5. Create your next release branch 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.

Have questions or suggestions? Feel free to reach out to me on BlueSky or LinkedIn. I'm always excited to discuss better ways to help teams ship code more efficiently ✌️

Next Steps

  1. Read how to apply a GitHub commit patch file (How-To Article)
  2. Learn all the PHP 101 basics (Free Course, 13,000+ Students)
  3. Join our free monthly newsletter (9,000+ Developers)