Branching Strategy for Releases in GitLab CI/CD: Pipelines with Rules & Merge Requests
1. Why Do We Need a Release Branching Strategy?
In basic CI/CD setups, every time a developer pushes code to any branch, the exact same pipeline runs, including high-risk production jobs like deployment or release publishing. This creates a severe operational risk: accidental triggers. If any developer pushes code and mistakenly clicks or triggers a manual release job, an unintended release goes out to production, triggering complex rollbacks and production outages.
As teams and projects scale, running release jobs on every random feature branch becomes unsustainable. A more robust, industry-standard approach is needed to separate Lower Branches (where daily development happens) from Release/Default Branches (where controlled, production-ready releases occur).
2. Core Mental Model: Lower Branches vs. Release Branches
The creator uses a multi-tiered structural model to separate everyday developer experimentation from production delivery.
Real-Life Analogy: The Construction Site and the Final Blueprint
Think of a Feature Branch (fb_*) like a construction worker's temporary workshop. Workers are constantly building, tearing down walls, testing tools, and making noise. You wouldn't open this messy workshop to the general public.
Once the feature is fully inspected and approved, the final finalized blueprint is submitted via a formal request (Merge Request) to the master architect. Only inside the secure, official building (Release Branch / Default Branch) are official, finished products officially packaged and shipped out to clients.
3. Naming Conventions and Team Standards
To automate pipeline behavior cleanly, teams establish strict prefix conventions for their branches:
4. The Complete Lifecycle Workflow
To move code safely from a local developer's workspace to a production release, GitLab CI/CD follows a strict multi-step workflow governed by Merge Requests (MRs).
5. Implementing Rules in GitLab CI/CD Configuration
To enforce that release jobs only run on release or default branches—and that heavy release jobs never execute on raw developer feature branches—we use GitLab CI/CD rules.
A. Securing the Release Job
We configure the release job so that it evaluates the current branch name and execution context. If the branch starts with rb_ or is the default branch, it includes the job as a manual action. Otherwise, it sets the rule to never.
maven_release_job:
stage: release
script:
- echo "Executing Maven Release..."
- mvn release:prepare release:perform
rules:
- if: '$CI_COMMIT_BRANCH =~ /^rb_/'
when: manual
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
when: manual
- when: never
Note: Setting
when: manualon production release jobs acts as a crucial human safety barrier. Even if a pipeline runs automatically, the actual release step pauses and waits for an explicit human click, preventing accidental automated disasters.
B. Isolating MR Pipelines and Lower Branch Jobs
For lower feature branches (fb_*), we want fast feedback (compilation checks and unit tests) without cluttering the pipeline with release logic. We can explicitly exclude jobs when pipelines are triggered by Merge Request events using predefined GitLab variables like $CI_PIPELINE_SOURCE == "merge_request_event".
build_job:
stage: build
script:
- echo "Building project..."
- mvn compile
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: never # Exclude or customize specifically if needed
- when: always # Run on standard branch commits
test_job:
stage: test
script:
- echo "Running unit tests..."
- mvn test
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: never
- when: always
6. Step-by-Step Practical Demonstration Walkthrough
-
Working on the Feature Branch (
fb_login):- Developers push multiple commits daily to
fb_login. - The GitLab Pipeline editor displays only low-level jobs (
build,test,snapshot_publish). Themaven_release_jobis entirely hidden because the branch name starts withfb_(violating the release branch rule).
- Developers push multiple commits daily to
-
Creating an Empty Release Branch (
rb_login):- A maintainer creates a corresponding release branch named
rb_loginbranched frommain.
- A maintainer creates a corresponding release branch named
-
Raising a Merge Request (MR):
- The developer navigates to GitLab and creates a New Merge Request with
Source: fb_loginandTarget: rb_login. - GitLab automatically spins up an MR Pipeline. This isolated pipeline verifies that the proposed changes compile cleanly without breaking syntax before merging.
- The developer navigates to GitLab and creates a New Merge Request with
-
Code Review & Approval:
- Team reviewers inspect the code diff within the GitLab MR interface. Once satisfied, the reviewer clicks Approve and Merge.
-
Triggering the Release Pipeline:
- The moment the code merges into
rb_login, a brand-new pipeline initiates on the release branch. - Because the branch prefix is now
rb_, the pipeline successfully evaluates our rules and includes themaven_release_jobin a manual state, ready for a secure, human-verified deployment.
- The moment the code merges into
7. Industry Best Practices & Production Safety Mindset
- Protect Release & Default Branches: Restrict direct push access on branches like
main,master, andrb_*inside GitLab Repository Settings. Force developers to always use Merge Requests. - Always Keep Release Jobs Manual: Even if your deployment automation is mature, keep production release and deployment tasks as manual triggers (
when: manual). This prevents unexpected bugs from instantly hitting production if an unintended pipeline gets triggered. - Leverage Predefined Variables: Utilize built-in GitLab CI environment variables (such as
$CI_COMMIT_BRANCH,$CI_DEFAULT_BRANCH, and$CI_PIPELINE_SOURCE) to dynamically control job visibility across different branch types. - Fail Fast with MR Pipelines: Use MR pipelines to catch compilation errors, lint failures, and basic unit test regressions before code touches the release branch.
8. Summary and Key Takeaways
- Avoid Single-Pipeline Traps: Never run production release jobs on raw developer feature branches to eliminate accidental releases.
- Branch Naming Conventions: Adopt clear prefixes like
fb_*for feature/lower branches andrb_*for release branches to establish structural clarity. - Role Separation: Lower branches are maintained by day-to-day developers for rapid iteration; release branches are tightly controlled and protected by senior maintainers.
- GitLab CI Rules: Use
$CI_COMMIT_BRANCH =~ /^rb_/and$CI_DEFAULT_BRANCHchecks inside jobrules:blocks to conditionally include or exclude deployment jobs. - Merge Request (MR) Pipelines: Run targeted verification pipelines during MR creation to validate code health before merging into staging or production branches.
- Manual Safety Gates: Always configure critical production release jobs with
when: manualto ensure human oversight and prevent costly production rollbacks.