GitHub Flow: Simple Branching for Continuous Delivery
Learn GitHub Flow — the lightweight branching strategy built for continuous deployment. Covers feature branches, pull requests, and production deployment on every merge.
GitHub Flow: Simple Branching for Continuous Delivery
GitHub Flow is a response to the complexity of Git Flow. Where Git Flow defines five branch types with specific lifecycles, GitHub Flow has exactly one rule: everything starts from main, and everything merges back to main.
Created by GitHub as their internal workflow, this model assumes you can deploy from main at any time. There are no release branches, no version branches, no staging branches. If your code passes tests on a feature branch, it merges to main and deploys.
This post explores why some of the fastest-moving teams in the world use this model, where it breaks down, and how to implement it without shooting yourself in the foot.
When to Use / When Not to Use
Use GitHub Flow When
- Continuous deployment — You deploy to production multiple times per day or week
- Web applications and SaaS — No packaging, signing, or app store approval process
- Small to medium teams — Where communication overhead of complex branching outweighs benefits
- Feature flag culture — You control feature rollout with flags, not branch timing
- Automated testing — Your CI pipeline catches regressions before they reach
main
Do Not Use GitHub Flow When
- Scheduled releases — You ship on a calendar cadence with QA gates
- Multiple supported versions — You need to patch v1.x while developing v2.x
- Regulated environments — You need formal change management and release documentation
- No CI/CD pipeline — Without automated testing, every merge to
mainis a gamble - Mobile or desktop apps — Where each release requires a build and distribution process
Core Concepts
GitHub Flow has exactly three concepts:
| Concept | Description |
|---|---|
main branch | Always deployable. The single source of truth. |
| Feature branches | Branch from main, merge back to main via pull request |
| Pull requests | Code review, discussion, and CI validation before merge |
That’s it. No release branches. No develop branch. No version branches. The simplicity is the entire point.
graph LR
A[main - Always Deployable] --> B[feature/new-ui]
A --> C[feature/api-v2]
A --> D[feature/bugfix]
B -->|PR + CI| A
C -->|PR + CI| A
D -->|PR + CI| A
A --> E[Deploy to Production]
Architecture and Flow Diagram
The complete GitHub Flow lifecycle from branch creation through production deployment:
graph TD
A[main - Production] -->|create branch| B[feature/branch]
B -->|commit| B
B -->|push| C[Remote Feature Branch]
C -->|open PR| D[Pull Request]
D -->|code review| E[Review Feedback]
E -->|update| C
D -->|CI passes| F[Approved]
F -->|merge| A
A -->|auto deploy| G[Production]
Step-by-Step Guide
1. Create a Feature Branch
Always branch from the latest main:
# Ensure you have the latest main
git checkout main
git pull origin main
# Create and switch to your feature branch
git checkout -b feature/add-user-profile
Branch naming conventions matter for traceability:
# Good: descriptive with ticket reference
feature/PROJ-123-add-user-profile
feature/add-dark-mode
bugfix/fix-login-timeout
# Bad: vague or personal
feature/work-in-progress
johns-branch
temp-fix
2. Commit and Push Frequently
Small, focused commits make code review easier and bisect more effective:
# Make changes
git add src/components/UserProfile.tsx
git commit -m "feat: add user profile component skeleton"
git add src/styles/profile.css
git commit -m "feat: style user profile card"
# Push to remote
git push -u origin feature/add-user-profile
3. Open a Pull Request
The pull request is the heart of GitHub Flow. It’s where code review, automated testing, and team discussion converge:
# Using GitHub CLI
gh pr create \
--base main \
--head feature/add-user-profile \
--title "feat: add user profile page" \
--body "## Summary
Adds user profile page with avatar, bio, and settings link.
## Testing
- Manual: /profile endpoint renders correctly
- Unit: UserProfile component tests pass
- E2E: Profile navigation flow verified
## Screenshots
[Attach screenshots if UI changes]"
4. Review and Iterate
Code review is mandatory in GitHub Flow — it’s the only quality gate before main:
- At least one approved review required
- All CI checks must pass (tests, linting, type checking)
- Address review comments with additional commits to the same branch
- The PR updates automatically with each push
5. Merge and Deploy
Once approved and green:
# Merge via CLI or UI
gh pr merge feature/add-user-profile --squash --delete-branch
# The merge triggers deployment
# CI/CD pipeline detects main branch update and deploys
Most teams use squash merges to keep main history clean, but some prefer merge commits for topology preservation.
Production Failure Scenarios + Mitigations
| Scenario | What Happens | Mitigation |
|---|---|---|
| Bad merge reaches production | A bug slips through review and CI | Feature flags allow instant rollback without code changes; revert the merge commit |
| CI is green but production breaks | Tests don’t cover the failure mode | Add canary deployments; monitor error rates post-deploy; implement automated rollback |
| Conflicting PRs | Two PRs modify the same file, only one can merge first | Communicate in PR comments; rebase the second PR on updated main before merging |
| Long-running feature branch | Branch diverges from main, merge conflicts pile up | Rebase on main daily; break large features into smaller, mergeable chunks |
| Deploy pipeline failure | Merge succeeds but deployment fails | Deployment should be atomic; failed deploys auto-rollback; never leave main in a broken state |
Deployment Verification Checklist
After each merge to main, verify the deployment succeeded:
- CI pipeline completed with all green checks
- Deployment logs show successful rollout with correct commit SHA
- Health endpoint responds with 200 OK
- Error rate in monitoring is within normal baseline (< 1% increase)
- Key user journeys pass smoke tests (login, core feature, checkout)
- No new Sentry/alerting errors in the last 5 minutes
- Database migrations (if any) completed successfully
- Feature flags for the new feature are in the intended state
- Rollback procedure is documented and tested if this is a high-risk deploy
Trade-offs
| Aspect | Advantage | Disadvantage |
|---|---|---|
| Simplicity | One permanent branch, easy to understand | No structure for complex release management |
| Speed | Merge and deploy in minutes | Requires excellent automated testing |
| Code review | Every change gets reviewed before merge | Can become a bottleneck for large teams |
| History | Clean linear history with squash merges | Loses feature branch topology information |
| Risk | Small, frequent changes are low-risk | Every merge to main is a production change |
| Learning curve | New developers can start contributing immediately | Requires discipline around CI and feature flags |
Implementation Snippets
Branch Protection Rules
# GitHub repository settings (via API or UI):
# Required pull request reviews: 1
# Dismiss stale reviews: enabled
# Require status checks: build, test, lint
# Require branches to be up to date: enabled
# Include administrators: enabled
# Restrict pushes that create matching files: enabled
GitHub Actions — CI Pipeline
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test -- --coverage
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to production
run: ./scripts/deploy.sh
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
Feature Flag Integration
// src/features/userProfile.ts
import { isFeatureEnabled } from '@/utils/featureFlags';
export function UserProfilePage() {
if (!isFeatureEnabled('user-profile')) {
return null; // Or redirect to a fallback
}
return <UserProfile />;
}
// Feature flag evaluation
// src/utils/featureFlags.ts
export function isFeatureEnabled(flag: string): boolean {
const flags = process.env.FEATURE_FLAGS || '{}';
return JSON.parse(flags)[flag] === true;
}
Quick Revert Script
#!/bin/bash
# scripts/revert-last-merge.sh
# Revert the last merge commit on main
git checkout main
git pull origin main
# Find the last merge commit
LAST_MERGE=$(git log --oneline --merges -1 --format="%H")
if [ -z "$LAST_MERGE" ]; then
echo "No merge commits found on main"
exit 1
fi
echo "Reverting merge: $LAST_MERGE"
git revert -m 1 "$LAST_MERGE"
git push origin main
echo "Revert pushed. CI will redeploy the previous version."
Observability Checklist
- Logs: Log every PR merge event with author, PR number, and commit SHA
- Metrics: Track PR cycle time (open to merge), deployment frequency, and mean time to recovery
- Traces: Correlate each deployment with the PR that triggered it and any subsequent incidents
- Alerts: Alert on deployment failure rate > 5%, PRs open longer than 3 days, or
mainbranch CI failures - Dashboards: Display deployment frequency, lead time for changes, change failure rate, and MTTR (DORA metrics)
Security and Compliance Notes
- Branch protection: Required reviews and status checks prevent unauthorized merges to
main - Signed commits: Require GPG-signed commits for cryptographic authorship verification
- CODEOWNERS: Use
CODEOWNERSfile to require specific team reviews for sensitive directories - Secrets scanning: Enable GitHub secret scanning to prevent credential leaks in PRs
- Audit log: GitHub’s audit log tracks all repository events including branch protection changes
- Compliance gap: GitHub Flow lacks formal release artifacts — supplement with deployment logs and change tickets if required by your compliance framework
Common Pitfalls and Anti-Patterns
- The Staging Branch — Adding a
stagingbranch turns GitHub Flow into a half-baked Git Flow. Either commit to continuous deployment or use a different model. - Skipping Code Review — Merging your own PRs without review defeats the only quality gate. Enforce branch protection rules.
- Massive PRs — Pull requests with 50+ files are impossible to review effectively. Break features into smaller, reviewable chunks.
- Ignoring CI Failures — Allowing merges with failing checks means
mainis not always deployable. Make CI mandatory. - No Feature Flags — Without feature flags, every merged feature is immediately visible to users. This couples deployment to release.
- Long-Lived Branches — Feature branches that exist for weeks defeat the purpose. Merge small, merge often.
- Direct Commits to main — Bypassing PRs for “emergency fixes” creates inconsistency. Use the same process for everything.
Quick Recap Checklist
-
mainbranch is always deployable and represents production - Every change starts as a feature branch from
main - Pull requests require at least one review before merge
- CI pipeline runs tests, linting, and type checks on every PR
- Merged PRs trigger automatic deployment to production
- Branch protection rules prevent direct pushes to
main - Feature flags control feature visibility independent of deployment
- PRs are kept small and focused for effective code review
- Failed deployments trigger automatic rollback
- DORA metrics are tracked and reviewed regularly
Interview Q&A
GitHub Flow has one permanent branch (main) while Git Flow has two (main and develop) plus temporary release and hotfix branches. GitHub Flow assumes every merge to main is deployable, making it ideal for continuous deployment. Git Flow assumes releases are scheduled events requiring a stabilization period on a release branch.
In practice, GitHub Flow is simpler and faster but requires stronger automated testing and feature flag infrastructure to manage risk.
Create a hotfix branch from main, fix the bug, open a PR, get it reviewed and merged — the same process as any other change. The speed comes from the small scope of the fix, not from bypassing the process.
If the fix needs to go out immediately, use a feature flag to disable the broken feature while the fix goes through the normal pipeline. This is faster than waiting for a merge and deployment cycle.
Feature flags decouple deployment from release. You can merge code to main and deploy it to production without exposing the feature to users. This allows you to deploy frequently while controlling when features become visible.
They also enable progressive rollout — enabling a feature for 10% of users, then 50%, then 100% — and instant rollback by toggling the flag off without deploying new code.
Squash merge is the most common choice in GitHub Flow because it keeps main history clean and linear — each PR becomes a single commit. This makes git log easier to read and git bisect more straightforward.
Merge commits preserve the feature branch topology, which is useful for understanding what changes shipped together. Teams that value history preservation over cleanliness may prefer this approach.
The key is consistency — pick one strategy and enforce it via repository settings.
Resources
- GitHub Flow documentation — Official GitHub guide
- GitHub Flow vs Git Flow — Atlassian’s comparison
- Feature Flags best practices — LaunchDarkly’s guide
- DORA Metrics — Four key metrics for software delivery
- Trunk-Based Development — Related approach that GitHub Flow approximates
Category
Related Posts
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.
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.
Git Release Branching and Hotfixes: Managing Versions in Production
Master release branching and hotfix strategies in Git. Learn version branches, emergency fixes, backporting, and how to manage multiple production versions safely.