GitLab Flow compared with GitFlow and GitHub Flow Jump to heading

Branching model discussions usually present two options: GitFlow, with its develop branch and release ceremonies, and GitHub Flow, with one branch and continuous deployment. GitLab Flow sits between them and is often the right fit for teams that find GitFlow heavy but cannot deploy every merge straight to production. It keeps GitHub Flowโ€™s single integration branch and short feature branches, then adds downstream branches only where reality demands them: environment branches when deployments are gated, release branches when several versions must be supported. Its central rule โ€” fixes go upstream first, then flow downstream โ€” prevents the most common drift problems of multi-branch models. This page compares the three and shows GitLab Flowโ€™s two variants, within GitFlow vs GitHub Flow comparison.

When to use this approach Jump to heading

  • GitHub Flow does not fit because production deploys need a gate, a schedule or approval.
  • GitFlow feels heavy โ€” the develop branch adds little โ€” but you need some structure beyond main.
  • You are deciding on a model and want to compare all three on the same terms.
  • You run environment promotion through Git, as in branch-per-environment GitOps patterns.

Step 1 โ€” See the three models side by side Jump to heading

The models differ in how many long-lived branches they have and what those branches mean.

GitFlow, GitHub Flow and GitLab FlowGitFlow has two permanent branches plus release and hotfix branches and suits scheduled versioned releases. GitHub Flow has only main and suits continuous deployment. GitLab Flow keeps main as the integration branch and adds environment or release branches downstream only when needed, with fixes flowing upstream first.Long-lived branchesBest fitGitFlowmain + develop (+ release/hotfix)scheduled, versioned releasesGitHub Flowmain onlycontinuous deploymentGitLab Flow (env)main โ†’ staging โ†’ productiongated deploysGitLab Flow (release)main โ†’ release/X.Yseveral supported versionsGitLab Flow adds branches downstream of main, never a second integration branch GitFlow, GitHub Flow and GitLab FlowGitFlow has two permanent branches plus release and hotfix branches and suits scheduled versioned releases. GitHub Flow has only main and suits continuous deployment. GitLab Flow keeps main as the integration branch and adds environment or release branches downstream only when needed, with fixes flowing upstream first.Long-lived branchesBest fitGitFlowmain + develop (+ release/hotfix)scheduled, versioned releasesGitHub Flowmain onlycontinuous deploymentGitLab Flow (env)main โ†’ staging โ†’ productiongated deploysGitLab Flow (release)main โ†’ release/X.Yseveral supported versionsGitLab Flow adds branches downstream of main, never a second integration branch

Step 2 โ€” Environment branches: promote by merging downstream Jump to heading

In the environment variant, main is where features integrate, and branches named after environments represent what is deployed there. Promotion is a merge from upstream to downstream: main into staging, staging into production. Each environment branch deploys automatically when it changes.

# Promote what is on main to staging, then staging to production
git switch staging && git merge --ff-only origin/main && git push origin staging
# โ€ฆ after verification on staging โ€ฆ
git switch production && git merge --ff-only origin/staging && git push origin production
Environment branches in GitLab FlowFeatures merge into main. Main is promoted to the staging branch by fast-forward, which deploys to staging. After verification, staging is promoted to production by fast-forward. Each environment branch therefore always points at a commit that passed through the one upstream of it.promotion = fast-forward downstreammainABCDstagingABCproductionABfast-forward only โ€” an environment branch should never contain a commit main lacks Environment branches in GitLab FlowFeatures merge into main. Main is promoted to the staging branch by fast-forward, which deploys to staging. After verification, staging is promoted to production by fast-forward. Each environment branch therefore always points at a commit that passed through the one upstream of it.promotion = fast-forward downstreammainABCDstagingABCproductionABfast-forward only โ€” an environment branch should never contain a commit main lacks

Using fast-forward merges keeps each environment branch an exact prefix of the one upstream, so โ€œwhat is in productionโ€ is always a commit that has been on main and on staging. The deploy side is covered in promoting a release from staging to production.

Step 3 โ€” Release branches: cut from main, fix upstream first Jump to heading

In the release variant, release branches are cut from main when a version ships, and receive only cherry-picked fixes. The rule that distinguishes GitLab Flow from GitFlowโ€™s hotfix branches is upstream first: a fix is merged to main first, then cherry-picked to the release branches that need it.

git switch -c release/2.4 origin/main && git push -u origin release/2.4
# Later: a bug is found in 2.4
# 1. fix on main via a normal merge request
# 2. cherry-pick the merged fix to the release branch
git switch release/2.4 && git cherry-pick -x <fix-sha> && git push

Fixing upstream first guarantees the fix is never lost in the next release, because the next release is cut from main, which already has it. Recording the cherry-pick with -x is covered in recording backports with cherry-pick -x.

Step 4 โ€” Enforce the direction of flow Jump to heading

Both variants depend on changes moving in one direction. Protect downstream branches so they accept only merges from upstream, and check periodically that nothing exists downstream that is missing upstream.

# Anything on production or a release branch that main does not have is a problem
for b in origin/production origin/release/2.4; do
  echo "== $b"; git cherry -v origin/main "$b" | grep '^+' || echo "nothing missing upstream"
done
# Merge requests into production may only come from staging
check-source:
  rules: [ { if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "production"' } ]
  script:
    - '[ "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" = "staging" ] || { echo "promote via staging"; exit 1; }'

Step 5 โ€” Choose a model by how you ship Jump to heading

The choice follows from two questions: how often can you deploy to production, and how many versions must you support at once?

Choosing among the three modelsIf you deploy every merge to a single production version, use GitHub Flow. If you deploy a single version but production deploys are gated or scheduled, use GitLab Flow with environment branches. If you support several released versions, use GitLab Flow with release branches, or GitFlow if you also need a separate integration branch.How do you ship?every merge, one versionGitHub Flowmain onlygated deploys, one versionGitLab Flow (env)main โ†’ staging โ†’ prodseveral versions supportedGitLab Flow (release)or GitFlowstart with the simplest model your shipping constraints allow Choosing among the three modelsIf you deploy every merge to a single production version, use GitHub Flow. If you deploy a single version but production deploys are gated or scheduled, use GitLab Flow with environment branches. If you support several released versions, use GitLab Flow with release branches, or GitFlow if you also need a separate integration branch.How do you ship?every merge, one versionGitHub Flowmain onlygated deploys, one versionGitLab Flow (env)main โ†’ staging โ†’ prodseveral versions supportedGitLab Flow (release)or GitFlowstart with the simplest model your shipping constraints allow

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

Is GitLab Flow specific to GitLab? Jump to heading

No. It is a branching model and works on any forge. GitLabโ€™s documentation named and popularised it, and GitLabโ€™s environment features fit it naturally.

Can environment branches and release branches be combined? Jump to heading

Yes: release branches for versions, and environment branches below each if deployments are gated. Keep the number of long-lived branches as small as your constraints allow โ€” each one is something that can drift.

Why not just use deploy tags instead of environment branches? Jump to heading

Many teams do: deploy a tag to an environment and record it, instead of keeping a branch per environment. Branches suit GitOps tools that watch a branch; tags suit pipeline-driven deploys. Both work with the upstream-first rule.