GitLab 13 🦊 Controlling Job Execution with rules, only, and except
A pipeline that runs the same jobs on every branch, every commit, and every tag is a pipeline that wastes resources and delays feedback. The rules, only, and except keywords control which jobs run and when. The LFCS exam does not test GitLab, but the GitLab Certified Associate exam does. The critical distinction is that rules is the modern mechanism, and only/except are the legacy mechanism. Both are still supported, but rules is more expressive and is the recommended approach for new pipelines. A job with a rules clause evaluates conditions in order, and the first match determines whether the job runs, is delayed, or is excluded. A job without rules runs by default.
Key point: rules replaces only and except. A rules list is evaluated top to bottom. The first rule with a matching if condition (or no if, which matches everything) determines the job’s behavior. If no rule matches, the job does not run. The when keyword within a rule controls the outcome: on_success, on_failure, always, never, manual, or delayed. The only and except keywords still work but are less flexible.
Why execution control matters
The resource problem. Every job consumes runner minutes. A pipeline that runs deployment jobs on every feature branch, or integration tests on every documentation change, wastes money and time. The rules keyword lets the pipeline run the correct jobs for the correct context. A deploy job runs only on the main branch. An integration test runs only when the source code changes, not when the README changes .
The feedback problem. A merge request pipeline should run the tests that validate the proposed change. A scheduled pipeline should run the full suite, including the jobs that are too slow for every commit. The rules conditions can check the pipeline source ($CI_PIPELINE_SOURCE), the branch name ($CI_COMMIT_BRANCH), the tag ($CI_COMMIT_TAG), the file changes ($CI_COMMIT_BEFORE_SHA with changes), and the merge request target ($CI_MERGE_REQUEST_TARGET_BRANCH_NAME). This allows the pipeline to behave differently depending on why it was triggered.
The duplicate pipeline problem. A project with both branch pipelines and merge request pipelines can create two pipelines for the same commit: one for the push to the branch, and one for the merge request event. The workflow:rules keyword controls whether the pipeline is created at all. A common pattern is to skip branch pipelines when a merge request exists, which eliminates the duplicate and reduces the load on the runners .
The legacy problem. The only and except keywords were the original mechanism. They accept branch names, tags, and special keywords like merge_requests, branches, tags. They are still supported, but they cannot express complex conditions like “run if the branch is main AND the pipeline source is a schedule.” The rules keyword can, and it is the recommended approach for new pipelines.
The trade-off. rules is more complex to read than only/except, but it is also more precise. A rules list that is well-structured is easier to maintain than a combination of only and except that are scattered across jobs. The exam tests both, but the practical recommendation is rules.
a. The rules keyword: conditions and outcomes
The rules keyword is a list of rules. Each rule can have an if condition and a when outcome. The rules are evaluated in order. The first match determines the job’s behavior.
deploy:
script: ./deploy.sh
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: on_success
- if: $CI_COMMIT_TAG
when: manual
- when: never
The deploy job runs on main automatically, on a tag it runs manually, and on any other branch it does not run. The final when: never is the fallback: without it, a job with no matching rule is excluded by default, but the explicit fallback makes the intent clear.
The if condition can use any of the predefined variables. The most common are $CI_COMMIT_BRANCH, $CI_COMMIT_TAG, $CI_PIPELINE_SOURCE, and $CI_MERGE_REQUEST_TARGET_BRANCH_NAME.
test:
script: npm test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
The test job runs on merge request pipelines and on the main branch. It does not run on other branches.
The changes keyword within a rule matches when specific files have changed. This is the mechanism for running a job only when the relevant files are modified.
docs:
script: make docs
rules:
- changes:
- docs/**/*
The docs job runs only when a file in the docs/ directory is modified. The pattern is a glob. The changes keyword works only in branch pipelines and merge request pipelines. It does not work in scheduled pipelines, because there is no previous commit to compare against .
b. The when outcomes
The when keyword within a rule determines what happens when the rule matches. The default is on_success, which means the job runs if the previous stages succeed. The other values are on_failure, always, never, manual, and delayed.
cleanup:
script: ./cleanup.sh
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: always
The cleanup job runs regardless of whether the previous jobs succeeded or failed. This is the correct setting for cleanup jobs that must run even when the build fails.
The when: manual setting makes the job require a manual trigger. The job appears in the pipeline but does not run until a user clicks the play button.
deploy:
script: ./deploy.sh
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
The deploy job is created on the main branch but does not run automatically. This is the mechanism for a production deployment that requires an approval.
The when: never setting excludes the job entirely. It does not appear in the pipeline.
c. The only and except keywords
The only and except keywords are the legacy mechanism. The only keyword specifies when the job runs. The except keyword specifies when it does not.
test:
script: npm test
only:
- main
- merge_requests
except:
- schedules
The test job runs on the main branch and on merge requests, but not on scheduled pipelines. The only and except accept branch names, tags, and special keywords: branches, tags, merge_requests, schedules, pushes, web, api.
The only:variables syntax allows variable-based conditions. The only:changes syntax allows file-change conditions. These are the legacy equivalents of the rules conditions, but they are less flexible and are not recommended for new pipelines.
test:
only:
variables:
- $CI_COMMIT_BRANCH == "main"
The only and except keywords are deprecated but still supported. They are slower than rules because they require a separate evaluation pass, and they cannot express the complex conditions that rules can. The exam tests them because they appear in existing pipelines, but the correct approach for new pipelines is rules.
Complete Example Session
# ============================================
# PART 1: BASIC RULES
# ============================================
deploy:
script: ./deploy.sh
rules:
- if: $CI_COMMIT_BRANCH == "main"
# ============================================
# PART 2: RULES WITH FALLBACK
# ============================================
deploy:
script: ./deploy.sh
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: on_success
- when: never
# ============================================
# PART 3: MERGE REQUEST RULES
# ============================================
test:
script: npm test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
# ============================================
# PART 4: FILE-CHANGE RULES
# ============================================
docs:
script: make docs
rules:
- changes:
- docs/**/*
# ============================================
# PART 5: MANUAL DEPLOY
# ============================================
deploy:
script: ./deploy.sh
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
# ============================================
# PART 6: ALWAYS RUN CLEANUP
# ============================================
cleanup:
script: ./cleanup.sh
rules:
- when: always
# ============================================
# PART 7: LEGACY ONLY/EXCEPT
# ============================================
test:
script: npm test
only:
- main
- merge_requests
except:
- schedules
# ============================================
# PART 8: WORKFLOW RULES
# ============================================
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
- when: never
# ============================================
# PART 9: VERIFY IN PIPELINE EDITOR
# ============================================
# Build → Pipeline editor → Lint
# Shows whether each job will run.
The nine parts trace the rules workflow: basic rules, fallback, merge request rules, file-change rules, manual deploy, always cleanup, legacy only/except, workflow rules, and editor validation.
Quick Reference
rules Keywords
| Keyword | Purpose |
|---|---|
if | Condition |
when | Outcome |
changes | File change pattern |
exists | File existence pattern |
allow_failure | Allow failure |
when Values
| Value | Meaning |
|---|---|
on_success | Run if previous stages pass |
on_failure | Run only on failure |
always | Always run |
never | Never run |
manual | Manual trigger |
delayed | Delayed start |
Common Variables
| Variable | Value |
|---|---|
$CI_COMMIT_BRANCH | Branch name |
$CI_COMMIT_TAG | Tag name |
$CI_PIPELINE_SOURCE | Trigger source |
$CI_MERGE_REQUEST_TARGET_BRANCH_NAME | MR target |
Pipeline Sources
| Source | Meaning |
|---|---|
push | Branch push |
merge_request_event | Merge request |
schedule | Scheduled |
web | Manual trigger |
api | API trigger |
Legacy Keywords
| Keyword | Purpose |
|---|---|
only | When to run |
except | When not to run |
only:variables | Variable-based condition |
only:changes | File-change condition |
Best Practices
✅ Do This:
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
- ✅ Use
rulesfor new pipelines
rules:
- changes:
- docs/**/*
- ✅ Run jobs only when relevant files change
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
- when: never
- ✅ Control pipeline creation with
workflow:rules
❌ Don’t Do This:
# Mix rules with only/except
<!-- Conflicting behavior --> # ❌
# Omit the fallback when: never
<!-- Jobs run by default when no rule matches --> # ❌
# Use `changes` in scheduled pipelines
<!-- There is no previous commit to compare --> # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Job runs on every branch | No rules or only | Add rules |
| Job excluded unexpectedly | No matching rule | Add fallback or fix condition |
| Duplicate pipelines | Branch + MR pipelines | Use workflow:rules |
changes ignored | Scheduled pipeline | Use only:changes or remove |
| Job not running on MR | No merge_request_event rule | Add condition |
Real-World Examples
1. Main Branch Only
rules:
- if: $CI_COMMIT_BRANCH == "main"
2. Merge Request
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
3. Tag Only
rules:
- if: $CI_COMMIT_TAG
4. Manual
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
5. Always
rules:
- when: always
6. File Changes
rules:
- changes:
- src/**/*
7. Legacy Only
only:
- main
- merge_requests
8. Legacy Except
except:
- schedules
9. Workflow Rules
workflow:
rules:
- if: $CI_COMMIT_BRANCH == "main"
- when: never
10. Complex Condition
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
allow_failure: false
Visual
Rule Evaluation Flow
┌──────────────────────────────────────────────┐
│ Rule 1: if: $CI_COMMIT_BRANCH == "main" │
│ ├─ Match → job runs │
│ └─ No match → continue │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Rule 2: if: $CI_COMMIT_TAG │
│ ├─ Match → job runs manually │
│ └─ No match → continue │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Rule 3: when: never │
│ → job excluded │
└──────────────────────────────────────────────┘
Workflow Rules
┌──────────────────────────────────────────────┐
│ Pipeline trigger │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ workflow:rules │
│ ├─ MR event → create pipeline │
│ ├─ main branch → create pipeline │
│ └─ other → no pipeline │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Pipeline created or rejected │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Modern keyword | rules |
| Legacy keywords | only, except |
when values | on_success, on_failure, always, never, manual, delayed |
| File change condition | changes |
| Pipeline creation control | workflow:rules |
| Common variables | $CI_COMMIT_BRANCH, $CI_PIPELINE_SOURCE, $CI_COMMIT_TAG |
Key takeaways:
rulesis the modern mechanism. It evaluates conditions in order and determines whether the job runs, is delayed, or is excluded. Thewhen: neverfallback is the standard pattern .onlyandexceptare legacy. They are still supported but less flexible and slower. The exam tests them because they appear in existing pipelines.- The
workflow:ruleskeyword controls pipeline creation. It is the mechanism for preventing duplicate pipelines when both branch and merge request pipelines are enabled . - The
changeskeyword runs jobs only when relevant files are modified. It works only in branch pipelines and merge request pipelines. It does not work in scheduled pipelines. - The
when: manualoutcome creates a manual gate. The job is created but does not run until a user triggers it. This is the mechanism for production deployment approvals.
Remember: The rules keyword is the gate. The if condition is the check. The when outcome is the decision. The first matching rule wins. The fallback (when: never) is the safety net. The workflow:rules is the pipeline-level gate. The only/except is the legacy path. The exam tests the conditions and the outcomes. The practice is the only way to internalize the precedence.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!