Introducing git ship, a simplified git flow workflow

Want to learn something new? Check out my most recent Egghead course:

Build a Twelve-Factor Node.js App with Docker - WATCH NOW on!

Cheers -Mark

Sat, 04/07/2018 - 14:26

Submitted by markoshust Sat, 04/07/2018 - 14:26

Ever since I started using git, I was enamored by the nice streamlined process of git flow. It proved to be a very reliable workflow process for managing features, releases and fixes within a team environment, and added some much-needed structure to git.

Fast-forward 8 years, and it leaves much to be desired. At times I found myself endlessly merging branches, sometimes forgetting to create the numerous pull requests into different branches for hot fixes, and also wondering why we needed a develop branch at all. There must be a better way?

I was completing work on a Screencast Course for Docker, and the first lesson in that course is how to use git flow; codebase is the first step of building a Twelve-Factor App. Recording the screencast numerous times, I realized how tedious development in git flow could actually be, and noticed some possible shortcuts that could be taken, while still leaving the process intact. Over the last 6 months or so, I've built and concluded in a brand new simplified workflow called "git ship"; heavily inspired by git flow, b but focused on shipping code instead of merging branches.

Let's start with looking at git flow. If you have been using git for any amount of time on a team larger than zero, you have probably encountered this well thought-out workflow.

git flow workflow

However, you will see that there is merging everywhere; merging a feature branch into develop, merging develop into a release branch, and merging a release branch into develop. Oh, and I didn't even touch base with hotfixes; a simple hotfix requires sending pull requests into both develop and master branches, and given the above diagram, is never actually merged into a release branch. (Could it become lost along the way? Yes, yes it can.)

I started to question everything about this process. It appeared master branch really was underutilized, and the develop branch was over-utilized. I never really seemed to have issues with feature branches, but why did a hotfix follow a different workflow? It seemed as though I was merging branches way too much. Was there a way to ship software quicker, but following a similar workflow?

Meet git ship, a heavily simplified version of git flow:

diagram of git ship

Key takeaways:

  • new branch created for every release from master [release/1.1]
  • staging looks at current/active release branch [release/1.1]
  • when release branch code is ready to be released:
    • merge from release/1.1 into master
    • tag release [v1.1.0]
    • deploy to production from tag [git checkout tags/v1.1.0]
  • after every release, create next release branch from master [git checkout -b release/2.0 master]
  • when hotfixes are needed, branch the hotfix changeset from the specific release [git checkout -b feature/123 release/1.1]
  • pull requests for hotfixes merge from branch feature/123 into branch release/1.1
  • when all hotfixes are complete, merge release branch into master then deploy as usual with new tag [v1.1.1]
  • merge master back into active release branch [release/2.0]


  • any new commit pushed to release/* should trigger build and deploy to staging
  • new tag pushes trigger release to production

Master Branch

  • master only ever contains production-ready code
  • tags only made from master


  • Works best with agile/scrum-based methodologies
  • Doesn't work with continuous integration/deployment processes
  • Works well with small teams (projects with less than 10 developers).

You'll notice one big difference right away; gone is the develop branch. We're focusing entirely on releasing software on a timely schedule, so the develop branch really doesn't benefit us. Note that the concept of a "working tree" still does exist; however, it is based on whatever the release branch is at the time. I call this the "moving working tree".

The "moving working tree" concept only works if we are constantly pushing code forward. Code starts it's life on the master branch, which only ever contains production-ready code. Our number one priority is to ship code, so we immediately focus on this by creating a release branch which is branched from master. Feature branches are created off of this current release branch, and are iterated through as usual within a local development environment. The "staging" server always looks at the current release branch, and is where we test integrated code.

You can think of every release tied to a 2-week sprint. Since the goal of sprints is to ship code at the end of every sprint, this aligns perfectly. After you have fully tested your code on staging and it is ready to be released, you can merge the release branch into master, which is the only way to get code into master. Then, we tag the release, to signify a specific release on the branch at this point in time. We then deploy that tagged version of code to production.

After code is pushed to production, a new release branch is created from master, and the process is repeated. Let's say you have a code freeze for the last 2 days of the sprint, but what to start on the next release? No worries. Just create a new release branch based on the current release branch, and start coding. In this situation, we just need to make sure to merge the master branch back into the new release branch after code makes it's way into that branch. Note that since master only ever contains production-ready code, it is always ok to merge master back into the current release branch. But, we don't want to move code in master back into an old release branch; remember, we always move forward so the concept of our moving working tree stays in-tact.

Hotfixes also get tremendously easier. Now, a hotfix works exactly like any other feature, as long as we make sure to branch our new hotfix branch off of the release branch of where the fix is to be applied. But doesn't this break the concept of our moving working tree? Not at all, as once the code has been merged into the release branch and merged into master, we will pull that code forward and also merge it back into our current release branch.

Modern deployment processes such as Bitbucket Pipelines can be setup to look at wildcard branches. We have to make sure we setup all pushes to release/* to automatically build and release to our staging server. This is pretty neat, as our staging server will automatically look at what currently needs to be tested. A push of an old release branch will automatically push out code tied to that old release branch so we can test a hotfix. And also, any pushes of our current release branch will also be pushed to staging. These deployments can be passively managed through the merges of these branches; want to hold off for a little while longer to test that hotfix? No worries, just put pull requests on hold as long as needed, or setup an additional staging server for your complex needs and requirements.

You'll also want to make sure that the "main branch" within Github or Bitbucket is pointed at your current release branch, so pull requests automatically default to get merged into this release branch. This can be easily updated by a system administrator at the end of every sprint.

Questions? Comments? I'd love to hear feedback on this new workflow and your experiences with the updated process. I truly believe this updated workflow process can help mitigate unneeded complexity and help do what you do best as developers: ship software.


I have a problem with the notion that the release branches appear to live past the release. That is a bad idea since it allows someone to add commits to the branch after the release so the branch does not reflect what is released. But that is not necessary. The release branch should be deleted when matter is tagged and then if you need to do release 1.1.1 you create release/1.1.1 starting from the 1.1 tag.After that it basically boils down to what name you put on things and merging to existing branch vs. create new branch. I would much rather be able to say that development is done on develop branch rather than saying it by is on the highest numbered release branch.

Thanks for the input. Regarding having the release branch not reflect what is released, I don't think that is a problem at all, since releases are only tagged from master, and the tag is a snapshot of what is released. Prod never should look at or reference a release branch - only a tag from master. Also, naming release beaches with tertiary semantics (1.1.1 vs 1.1) creates unnecessary release branches - it doesn't make sense to create a separate release branch for every hotfix. This is one of the main reasons I created this workflow - to avoid creating unnecessary branches. The "development is done" phrase is also another main reason for this workflow. Development is never done. You could have a pull request ready for the next release before the current release has been pushed to prod. What are you supposed to do in that situation - just hold off merging PR's? This also goes against git flow, as they recommend release branches as well. We want to make the conveyer belt more efficient, not stop it all together.

Dude!... yes. Can I "have a beer" with you ;). .... I'm in Portland if you're down. You've expressed more dev sanity in that one post and that one reply than I've heard all year outside of my closest tech circles. Well done. Scripts / macros coming soon? best, Artemi

Thanks Artemi. I’m in the Cleveland Ohio area so not even close! Perhaps at a conference :) I don’t plan on any scripts because it is all fairy simple. I liked git flow’s scripts as a beginner, but over time growed to dislike them very much, as I didn’t understand what was going on “under the hood”. Also since this is so much simpler than flow, I don’t think it warrants any additional abstract over standard git. Thanks for the kind comments. People (especially programmers) overcomplicate absolutely everything.

Few things that doesn't sound right to me: > You'll also want to make sure that the "main branch" within Github or Bitbucket is pointed at your current release branch, so pull requests automatically default to get merged into this release branch. The release branch, to me, is the code that ready to be released but may not been tested. In the other word, it's staging only branch and may not ready for production. There will be some bug fixes happened. The Github default branch is set to master by default, and it shows the master branch to public when someone first time get the repo. If you open source your project, and other people trying to use it. There will be confusing when people have to switch to master branch (production ready branch). The most time, people using open source project will do `git clone repo` and `cd repo && ./deploy`. However, the release branch may not good for production yet. IMO, the default branch that showing to public will always be the production-ready branch. (If I misunderstanding your points there, sorry about it.) > Hotfixes also get tremendously easier. Now, a hotfix works exactly like any other feature, as long as we make sure to branch our new hotfix branch off of the release branch of where the fix is to be applied. The old git flow branch hotfixes from master branch (production-ready) and merged back to master branch and develop branch. It will only have "two main branches with an infinite lifetime", all other branches can be deleted after it's done. In your case, the hotfixes will be branched out from release branch. It sounds to me that we need to keep all release branches forever or keep at least two release cycles in case there is any hotfix. More branches? You said "new hotfix branch off of the release branch of where the fix is to be applied", what if you need hotfix the thing that release branch 1.0 did while you are developing 2.0? Thank you so much, it is good idea but simplify the most things, but something may not right in some cases.

I agree with you that release branches won't necessarily contain production-ready code, but are used as an area to stage an upcoming release. In this situation, you can absolutely keep "master" as the main branch for open source projects (definitely the way to go), as I believe you can change the destination of pull requests with GitHub if you are the maintainer (changing always to the current release branch). This setup was mainly written up for private repos that ship on schedule. That said, it should work just fine for open source projects and keeping the "master" branch the main/default branch. You are correct with needing to keep release branches. I'd keep them as long as you are supporting that release version, we don't really pay for disk space for keeping release branches in git, so don't think there is any harm keeping them around at least until you want to do some spring cleaning. If you wanted to hotfix release 1.0 while you are on 2.0, you can take care of that depending on if you were going to create another tag off the 1.0 release branch or not. If you were planning on another 1.x release, you would branch off release/1.0, commit your updates, merge back into that branch, then merge into master and tag & release. Since master has then been updated, you can merge from master into release/2.0, then your hotfix will be back in the 2.0 release branch. Hope this helps :) I can see some use-cases where git ship won't work: continuous deployments being the main reason. But you can most likely adapt git ship to your workflow depending on the situation.

Hiya! I'm pretty new to using git, and have really only messed with it for publishing to GitHub Pages. That said, I have a couple remarks and a couple questions. First, you say that hotfixes in `git flow` are never merged into the release branch. It's not in the diagram but Vincent *does* address this in the body of his post: >The one exception to the rule here is that, when a release branch currently exists, the hotfix changes need to be merged into that release branch, instead of develop. Back-merging the bugfix into the release branch will eventually result in the bugfix being merged into develop too, when the release branch is finished. (If work in develop immediately requires this bugfix and cannot wait for the release branch to be finished, you may safely merge the bugfix into develop now already as well.) Not sure if that clarifies anything, but wanted to share. Second, I noticed that the flow in the left-most feature branches in your diagram never makes its way back to the release branch. Is this just backwards arrows in the diagram or is there a mechanism I'm missing? Third, I *think* when you say "After code is pushed to production, a new release branch is created from develop, and the process is repeated" you mean "After code is pushed to production, a new release branch is created from master, and the process is repeated." Yeah? Fourth and last, do you find deciding on the version when you create a new release branch, rather than when all features have been merged into master, creates confusion? It would seem you'd want to decide on your version number after all the features have been developed, not before. Thanks for writing this up! Cheers.

For your first point, check out the diagram and explanation - hotfixes are definitely merged into release branches, that's actually their only path to get into the repository. About point two, I think you may have a cached version of the image that contained a bug, there should definitely be arrows going back to the release branch. Thanks for point three, I fixed the typo as there is no develop branch in this process. With your fourth point, there should be no confusion when creating your next release branch -- it's either a major release, or a minor release. Note that this process is geared towards agile teams that focus on delivery every two weeks, and knowing what you will be developing next is part of the process.

Hi, I like the ideas presented. I have similar gripes with git-flow as you. One thing I am wondering about is whether or not you really need all the release branches. Why not just keep develop and let it run forever? The master+develop+feature workflow is essentially [Github Flow]( with a staging branch called develop. You can tag releases from master as you merge back into develop. If you later need to do something with an old release, you can always branch from that tag and run with that release branch. The approach of having only develop+master+feature branches and tags is sort of what happens anyway when people start getting lazy with git-flow. It seems to work OK if you deploy to staging from develop and you don't have to have a build-master/release-master re-jiggering your Jenkins server every 2 weeks. If you have overlapping development like you described then creating a future release branch would work but you could also do something like having 2 develop branches Red/Green or something like that so you can swap back and forth if you need to have a separate release for QA to work on.

I think your approach could absolutely work. In my workflow I typically assume that I will always continually be updating two branches at any given time (live site vs site in dev), so you wind up creating release branches anyways. I'd like to skip all that and assume i'm just creating a release branch right from the get-go (I'm going to do it anyway). This leaves develop branch in a precarious situation where it is used, but adds complexity to the whole model, and it really isn't needed. This setup is geared towards staged releases, not CI or red/green deployments. I think having the formality in structure in place from the beginning actually simplifies all of this.

What do you do if a feature is not completed and can't be merged into the release it branched off of?

Really nice model! It fixes most of my pain points with Git Flow. Thanks! I adapted it in our case to follow Semantic Version ( and allow for Alpha and Beta releases right off the Release branch. One nice thing about semver is that because MINOR and PATCH releases are guaranteed to be backwards compatible, you don't need hotfixes unless you plan to support previous MAJOR versions. Here's the chart we'll probably end up using internally: Cheers & thanks again!

Great, glad you find it useful! I have yet to see a use case that works for git flow but doesn't work on git ship. Your setup is good, very similar :)

I really like this workflow. Searching for a good workflow for a while now. Are there any plans to create some helper scripts like git-flow have? I'm interested in it and would also help work on it.

Add new comment