Branches are flowing, staging works. But anyone with Maintainer access can still push directly to main. Fix that.
This guide locks down main so that:
- No one pushes directly to it
- Merges only come from
staging - Only a specific person can approve and merge
Why Protect the Main Branch?
main is production. Every commit should be tested, reviewed, and intentional. Without protection:
- A dev can accidentally push broken code to production
- Hotfixes bypass staging
- No audit trail of approvals
Branch protection forces changes through merge requests, approvals, and CI checks.
You need Maintainer or Owner access to configure branch protection rules in GitLab. If you’re a developer, ask your team lead to set this up.
Step 1: Protect the Main Branch
In your GitLab project:
- Navigate to Settings → Repository
- Expand the Protected branches section
- Find
mainin the list (or add it)
Set the following:
| Setting | Value |
|---|---|
| Allowed to merge | Maintainers |
| Allowed to push and merge | No one |
| Allowed to force push | Off |
Result:
- No one can push directly to
main— not even Maintainers - Only Maintainers can merge via merge requests
- Force push is disabled — history stays intact
# This will now be rejected for everyone:
git push origin main
# remote: GitLab: You are not allowed to push code to protected branches on this project.
Make sure at least one person (usually the project Owner) retains merge access. If you set “Allowed to merge” to “No one,” nobody will be able to merge — including you.
Step 2: Protect the Staging Branch Too
Same treatment for staging:
- In Protected branches, click Add protected branch
- Select or type
staging
| Setting | Value |
|---|---|
| Allowed to merge | Maintainers |
| Allowed to push and merge | No one |
| Allowed to force push | Off |
Both branches are now protected. All changes flow through merge requests:
No shortcuts.
Step 3: Require Merge Request Approvals
Branch protection stops direct pushes. It doesn’t require review. Add approvals.
Project-Level Approval Rules
- Go to Settings → Merge requests
- Scroll to Merge request approvals
- Click Add approval rule
| Field | Value |
|---|---|
| Rule name | Production Gatekeeper |
| Approvals required | 1 |
| Approvers | Select your team lead or release manager |
Every MR now needs approval from the designated person before merging.
Prevent Approval by the Author
In the same section, enable:
- Prevent approval by author — authors can’t approve their own MRs
- Prevent editing approval rules in merge requests — devs can’t remove the requirement
- Remove all approvals when commits are pushed — new commits reset approvals
These close common loopholes.
Step 4: Restrict Which Branches Can Merge to Main
GitLab has no built-in “only allow merges from branch X” setting. Use a CI check instead.
Add this to your .gitlab-ci.yml:
check-source-branch:
stage: .pre
script:
- |
if [ "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" = "main" ]; then
if [ "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" != "staging" ]; then
echo "❌ Error: Only the 'staging' branch can be merged into 'main'."
echo "Source branch: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"
echo "Please merge your changes to staging first."
exit 1
fi
echo "✅ Source branch is 'staging' — allowed to merge to main."
fi
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
Any MR from a non-staging branch to main fails immediately.
Require Pipeline to Pass Before Merging
Make this check block merges:
- Go to Settings → Merge requests
- Under Merge checks, enable:
- Pipelines must succeed
The enforced flow:
- Developer creates MR from feature branch to
develop developmerges tostaging(tested in staging environment)- Team lead creates MR from
stagingtomain - CI validates the source branch
- Team lead approves and merges
Any other path to main gets blocked.
Sometimes you need to bypass the normal flow for critical fixes. Create a hotfix/* branch from main, fix the issue, and merge it to both main and staging. Update the CI check to also allow hotfix/* branches:
if [ "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" != "staging" ] && \
[[ "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" != hotfix/* ]]; then
echo "❌ Only 'staging' or 'hotfix/*' branches can merge to main."
exit 1
fi Step 5: Limit Who Can Merge to Main
You’ve restricted what can merge. Now restrict who.
Using Allowed to Merge Setting
Step 1 set “Allowed to merge” to Maintainers. To limit it to a specific person:
- Go to Settings → Repository → Protected branches
- For
main, click Edit - Under Allowed to merge, select a specific user instead of a role
Only the designated release manager can merge to main, even if there are multiple Maintainers.
Using CODEOWNERS (GitLab Premium)
On GitLab Premium or Ultimate, use a CODEOWNERS file:
# CODEOWNERS file in the root of your repo
# Everything requires approval from the team lead
* @team-lead-username
Enable Require CODEOWNERS approval in Settings → Repository → Protected branches.
The code owner must approve before any merge goes through.
Putting It All Together
Summary of protections:
| Protection | What It Does |
|---|---|
| Protected branches | No direct pushes to main or staging |
| Merge request approvals | Team lead must approve before merge |
| CI source branch check | Only staging (or hotfix/*) can merge to main |
| Pipeline must succeed | CI must pass before merge is allowed |
| Specific merge access | Only the designated person can click merge |
The complete flow:
Quick Checklist
Verify everything is in place:
-
mainbranch protected — push set to “No one” -
stagingbranch protected - Approval rule created with specific approver
- “Prevent approval by author” enabled
- “Remove all approvals when commits are pushed” enabled
- CI job checks source branch for MRs targeting
main - “Pipelines must succeed” enabled
- Specific user set as allowed to merge (not just “Maintainers”)
Wrapping Up
This takes 10 minutes. Every production change becomes intentional, reviewed, and tested. Branch protection + approval rules + CI checks create a gate that’s hard to bypass accidentally. No single mechanism is enough alone.
If you haven’t set up your branching strategy yet, start with the Git Branching Strategy: A Practical Case guide — it covers the foundation this builds on.