GitLab Flow: Environment and Release-Based Branching
Master GitLab Flow — the branching strategy that combines Git Flow simplicity with deployment pipelines. Learn environment-based and release-based branching patterns.
GitLab Flow: Environment and Release-Based Branching
GitLab Flow sits between GitHub Flow and Git Flow. It keeps the simplicity of a single main branch but adds environment-based branches for teams that need deployment stage separation. Created by GitLab, it reflects the reality that many teams deploy through staging environments before reaching production.
The model comes in two variants: environment-based branching for teams with staging environments, and release-based branching for teams with scheduled releases. Both variants share the same core principle: code flows downstream from main through increasingly stable branches.
This post covers both GitLab Flow variants, when each makes sense, and how to implement them without creating the complexity trap that Git Flow is known for.
When to Use / When Not to Use
Use GitLab Flow When
- Staging environment required — You deploy to staging before production for validation
- Multiple deployment targets — Different branches map to different environments
- Scheduled releases with CI/CD — You want release branches but with automated pipelines
- GitLab users — Native integration with GitLab CI/CD and environment management
- Compliance requirements — You need environment separation for audit purposes
Do Not Use GitLab Flow When
- Pure continuous deployment — If you deploy every merge directly to production, use GitHub Flow
- No staging environment — If you only have production, environment branches add no value
- Complex release management — If you need hotfixes, support branches, and version maintenance, Git Flow may be more appropriate
- Small teams — The environment branch overhead may not be justified
Core Concepts
GitLab Flow has two variants, each with distinct branch structures:
Environment-Based Branching
| Branch | Purpose | Deployment Target |
|---|---|---|
main | Development and staging | Staging environment |
production | Production-ready code | Production environment |
Code flows from main to production via merge. Every merge to main deploys to staging. When staging is validated, main merges to production.
graph LR
A[feature branch] -->|merge| B[main - Staging]
B -->|merge| C[production - Live]
B --> D[Auto Deploy to Staging]
C --> E[Auto Deploy to Production]
Release-Based Branching
| Branch | Purpose | Deployment Target |
|---|---|---|
main | Active development | Pre-release / Nightly |
release/X.Y | Stable release branch | Production |
Similar to Git Flow but simpler: main is always the cutting edge, and release branches are cut for production versions.
graph LR
A[feature] -->|merge| B[main]
B -->|cut| C[release/1.0]
B -->|cut| D[release/2.0]
C --> E[Production 1.0]
D --> F[Production 2.0]
C -->|cherry-pick| G[release/1.0.1]
Architecture and Flow Diagram
The complete GitLab Flow with environment-based branching and CI/CD pipeline:
graph TD
A[Developer] -->|push| B[feature branch]
B -->|MR + CI| C{CI Pass?}
C -->|No| D[Fix and retry]
D --> B
C -->|Yes| E[Merge to main]
E --> F[Deploy to Staging]
F --> G[QA Validation]
G -->|Approved| H[Merge main to production]
H --> I[Deploy to Production]
H --> J[Create Release Tag]
Step-by-Step Guide
1. Environment-Based Setup
Start with the simplest variant — two branches mapping to two environments:
# Initialize repository with main branch
git checkout -b main
git push -u origin main
# Create production branch
git checkout -b production
git push -u origin production
# Set up branch protection
# main: requires MR approval, CI must pass
# production: requires MR from main only, manual approval
2. Feature Development
Features branch from main and merge back through merge requests:
# Create feature branch
git checkout main
git checkout -b feat/user-dashboard
# Develop and push
git add .
git commit -m "feat: add user dashboard with analytics"
git push -u origin feat/user-dashboard
# Open merge request
gitlab mr create \
--source-branch feat/user-dashboard \
--target-branch main \
--title "Add user dashboard" \
--description "Implements the user dashboard with analytics widgets"
3. Deploy to Staging
Every merge to main triggers staging deployment:
# .gitlab-ci.yml
stages:
- test
- build
- deploy-staging
- deploy-production
test:
stage: test
script:
- npm ci
- npm run lint
- npm test
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
build:
stage: build
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
deploy-staging:
stage: deploy-staging
script:
- ./scripts/deploy.sh staging
environment:
name: staging
url: https://staging.example.com
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
4. Promote to Production
When staging is validated, merge main into production:
# Create merge request from main to production
git checkout production
git merge main
git push origin production
# Or via GitLab CLI
gitlab mr create \
--source-branch main \
--target-branch production \
--title "Promote to production" \
--description "Staging validated. Ready for production deployment."
5. Release-Based Variant
For teams that need versioned releases:
# Cut a release branch from main
git checkout main
git checkout -b release/2.0
git push -u origin release/2.0
# Deploy release branch to production
# .gitlab-ci.yml deploys release/* branches to production
# Hotfix for release
git checkout release/2.0
git checkout -b hotfix/2.0.1
# Fix the bug
git commit -am "fix: resolve payment processing error"
git checkout release/2.0
git merge hotfix/2.0.1
git push origin release/2.0
# Merge hotfix back to main
git checkout main
git merge release/2.0
git push origin main
Production Failure Scenarios + Mitigations
| Scenario | What Happens | Mitigation |
|---|---|---|
| Staging passes, production fails | Environment difference causes production-only bug | Use identical infrastructure for staging and production; run smoke tests post-deploy |
| Production branch diverges | Hotfixes to production aren’t merged back to main | Enforce merge-back policy; CI checks that production is an ancestor of main |
| Release branch staleness | Old release branches accumulate without cleanup | Archive release branches after 2 versions; automate branch lifecycle management |
| Merge conflict during promotion | main to production merge has conflicts | Resolve conflicts in main first; never resolve in production |
| Pipeline bottleneck | Long CI pipelines delay staging deployments | Parallelize test suites; use incremental builds; cache dependencies |
Trade-offs
| Aspect | Advantage | Disadvantage |
|---|---|---|
| Environment mapping | Clear branch-to-environment relationship | Extra merge step for production |
| Simplicity | Fewer branch types than Git Flow | More complex than GitHub Flow |
| Staging validation | Dedicated environment for QA | Requires maintaining staging infrastructure |
| Release management | Release branches for versioned software | Manual branch cutting and maintenance |
| CI/CD integration | Native GitLab CI/CD pipeline support | Less portable to other CI systems |
| Audit trail | Clear promotion path from staging to production | Additional merge commits in history |
Implementation Snippets
GitLab CI/CD — Full Pipeline
# .gitlab-ci.yml
stages:
- test
- build
- deploy-staging
- deploy-production
- verify
variables:
NODE_VERSION: "20"
.test-template: &test-config
image: node:${NODE_VERSION}
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
before_script:
- npm ci
unit-test:
<<: *test-config
stage: test
script:
- npm run test:unit
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == "main"'
integration-test:
<<: *test-config
stage: test
script:
- npm run test:integration
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
build:
<<: *test-config
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 week
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
- if: '$CI_COMMIT_BRANCH =~ /^release\/.*/'
deploy-staging:
stage: deploy-staging
script:
- ./scripts/deploy.sh staging
environment:
name: staging
url: https://staging.example.com
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
deploy-production:
stage: deploy-production
script:
- ./scripts/deploy.sh production
environment:
name: production
url: https://example.com
rules:
- if: '$CI_COMMIT_BRANCH == "production"'
when: manual
verify-production:
stage: verify
script:
- ./scripts/smoke-test.sh https://example.com
rules:
- if: '$CI_COMMIT_BRANCH == "production"'
Branch Protection via GitLab API
# Protect main branch
curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.example.com/api/v4/projects/$PROJECT_ID/protected_branches/main" \
--data "allowed_to_push[]=access_level:40" \
--data "allowed_to_merge[]=access_level:30" \
--data "allowed_to_push[]=access_level:30"
# Protect production branch (only merges from main)
curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.example.com/api/v4/projects/$PROJECT_ID/protected_branches/production" \
--data "allowed_to_push[]=access_level:40" \
--data "allowed_to_merge[]=access_level:40"
Environment Promotion Script
#!/bin/bash
# scripts/promote-to-production.sh
set -euo pipefail
echo "Promoting main to production..."
# Verify staging is healthy
STAGING_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://staging.example.com/health)
if [ "$STAGING_STATUS" != "200" ]; then
echo "ERROR: Staging health check failed (HTTP $STAGING_STATUS)"
exit 1
fi
# Create merge request
MR_URL=$(gitlab mr create \
--source-branch main \
--target-branch production \
--title "Promote $(git rev-parse --short HEAD) to production" \
--description "Staging validated. Commit: $(git log -1 --format='%h %s')" \
--json | jq -r '.web_url')
echo "Merge request created: $MR_URL"
echo "Awaiting approval for production deployment."
Observability Checklist
- Logs: Log every environment deployment with commit SHA, branch, and deployer identity
- Metrics: Track staging-to-production promotion time, deployment success rate per environment, and pipeline duration
- Traces: Trace each production deployment through staging validation to the original merge request
- Alerts: Alert when staging is unhealthy for > 1 hour, production deployment fails, or promotion queue exceeds 24 hours
- Dashboards: Display environment health, deployment frequency, promotion success rate, and pipeline lead time
Security and Compliance: GitLab Flow
- Environment separation: Production branch should have stricter access controls than main
- Manual approval: Require manual approval for production deployments in CI/CD pipeline
- Audit trail: GitLab’s merge request history provides a complete audit trail of promotions
- Secret management: Use GitLab CI/CD variables with environment scoping for environment-specific secrets
- Compliance: Environment-based branching naturally supports compliance requirements for staging validation before production
Environment-Based CI/CD Pipeline
# .gitlab-ci.yml — Full environment promotion pipeline
stages:
- test
- build
- deploy-staging
- deploy-production
- verify
variables:
NODE_VERSION: "20"
.test-template: &test-config
image: node:${NODE_VERSION}
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
before_script:
- npm ci
unit-test:
<<: *test-config
stage: test
script:
- npm run test:unit
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == "main"'
integration-test:
<<: *test-config
stage: test
script:
- npm run test:integration
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
build:
<<: *test-config
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 week
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
- if: '$CI_COMMIT_BRANCH =~ /^release\/.*/'
deploy-staging:
stage: deploy-staging
script:
- ./scripts/deploy.sh staging
environment:
name: staging
url: https://staging.example.com
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
deploy-production:
stage: deploy-production
script:
- ./scripts/deploy.sh production
environment:
name: production
url: https://example.com
rules:
- if: '$CI_COMMIT_BRANCH == "production"'
when: manual
verify-production:
stage: verify
script:
- ./scripts/smoke-test.sh https://example.com
rules:
- if: '$CI_COMMIT_BRANCH == "production"'
Common Pitfalls and Anti-Patterns
- The Staging Quagmire — Staging becomes a dumping ground for half-tested features. Only merge to main when features are complete and tested.
- Production Branch Divergence — Hotfixes applied directly to production without merging back to main cause the bug to reappear. Always merge back.
- Manual Deployment Drift — Manual deployments outside the pipeline create inconsistency. All deployments must go through CI/CD.
- Environment Configuration Drift — Staging and production configurations diverge over time. Use infrastructure as code to keep them identical.
- Release Branch Proliferation — Too many active release branches create confusion. Limit to 2-3 active releases maximum.
- Skipping Staging Validation — Auto-promoting from staging to production without validation defeats the purpose. Require explicit approval.
- Pipeline Coupling — Tying deployment to pipeline success without manual gates for production is risky. Use
when: manualfor production.
Quick Recap Checklist
-
mainbranch deploys to staging automatically -
productionbranch receives merges frommainonly - Every merge request requires CI validation
- Production deployments require manual approval
- Hotfixes merge back to both production and main
- Environment-specific secrets are scoped correctly
- Staging and production infrastructure are identical
- Deployment logs include commit SHA and deployer identity
- Release branches are archived after 2 versions
- Pipeline stages are parallelized for speed
Interview Q&A
Environment-based branching maps branches to deployment environments: main deploys to staging, production deploys to production. Code flows downstream from main through merge. This is ideal for web applications with continuous deployment.
Release-based branching maps branches to software versions: main is cutting-edge development, release/X.Y branches are stable versions deployed to production. This is ideal for versioned software with scheduled releases.
In environment-based GitLab Flow, hotfixes branch from production, fix the issue, merge back to production, and then merge to main to prevent regression.
In release-based GitLab Flow, hotfixes branch from the specific release/X.Y branch, merge back to that release, and then merge to main. This ensures the fix reaches both the current production version and future development.
Manual approval provides a human checkpoint between staging validation and production deployment. Even with comprehensive automated testing, some issues only surface in production-like environments. The approval step ensures someone has verified staging results, reviewed the changelog, and confirmed the deployment timing is appropriate.
This is distinct from GitHub Flow's fully automated deployment — GitLab Flow acknowledges that many organizations need a gate between staging and production for risk management.
GitLab Flow is simpler than Git Flow — it has fewer branch types and a clearer code flow direction. Git Flow has five branch types with complex merge patterns; GitLab Flow has two or three branches with downstream-only merges.
GitLab Flow also integrates more naturally with CI/CD pipelines, where each branch maps to a deployment stage. Git Flow was designed before CI/CD was ubiquitous and requires additional tooling to map branches to deployment environments.
Resources
- GitLab Flow documentation — Official GitLab guide
- GitLab CI/CD best practices — Pipeline configuration guide
- Environment-based deployments — GitLab environment management
- GitHub Flow vs GitLab Flow — Atlassian comparison
- Infrastructure as Code — Keep staging and production identical with Terraform
Category
Related Posts
Git Flow: The Original Branching Strategy Explained
Master the Git Flow branching model with master, develop, feature, release, and hotfix branches. Learn when to use it, common pitfalls, and production best practices.
Automated Changelog Generation: From Commit History to Release Notes
Build automated changelog pipelines from git commit history using conventional commits, conventional-changelog, and semantic-release. Learn parsing, templating, and production patterns.
Choosing a Git Team Workflow: Decision Framework for Branching Strategies
Decision framework for selecting the right Git branching strategy based on team size, release cadence, project type, and organizational maturity. Compare Git Flow, GitHub Flow, and more.