Chapter 08Lesson 03~145 minutes

workflow:rules, Job rules, if Expressions, changes, exists, Pipeline Sources, and Conditional Execution: Configuration, Design Choices, and Tradeoffs

Rules are executable policy, so readability and scope matter as much as syntax. This lesson compares workflow:rules with job rules, positive allowlists with catch-all fallthrough, changes with exists, explicit source guards with implicit assumptions, and simple ordered rules with clever expressions that are difficult to audit.

Design tradeoffsDuplicate pipelinescompare_toExpressionsAuditability

Learning objectives

  • Choose workflow:rules or job rules according to whether the requirement controls pipeline existence or only job membership.
  • Prefer explicit source allowlists and readable ordered rules over broad catch-all behavior when pipeline classes have different trust/cost.
  • Choose changes for diff-sensitive work and exists for repository-capability detection, with source/compare-to semantics made explicit.
  • Evaluate rule order, expression complexity, duplicate-pipeline prevention, and version-sensitive current GitLab behavior.
  • Document tier/offering/trust prerequisites and the observable evidence that proves each design choice.

1. Rules are policy code, not convenience syntax

A rule set should answer three audit questions quickly: which pipeline classes can exist, which jobs can exist inside each class, and what trusted evidence causes each decision? Designs that are technically compact but force reviewers to mentally simulate many overlapping fallthroughs are expensive to maintain and easy to mis-secure.

2. workflow:rules versus job rules

Requirement Best layer Why Evidence
Do not create pipelines for unsupported sources workflow:rules Stops work before any job graph exists Absence/presence of pipeline record by source
Avoid redundant branch pipeline when MR pipeline exists workflow:rules The duplication is pipeline-level One pipeline class for same push/SHA
Run integration tests only on MRs/default branch Job rules Other jobs can still use the pipeline Job graph membership
Make deployment manual on default branch Job rules Changes one job’s when/authorization path Job present as manual only in allowed context
Choose an include before full config exists include:rules + pre-pipeline variables Configuration composition decision Merged configuration

3. Positive allowlist versus broad fallthrough

A final when: always is easy to write and hard to bound. If new pipeline sources appear in your architecture later—API, child pipeline, multi-project pipeline, security policy pipeline—the catch-all can admit them without a deliberate review. A source allowlist is noisier but makes expansion an explicit code change.

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH'
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
    - when: never

The tradeoff is operational: strict allowlists can block a new legitimate integration until configuration is updated. That is often preferable for privileged/expensive repositories because failure is visible and reviewable rather than silently widening execution.

4. changes versus exists: diff intent versus repository capability

Question Use Example Failure mode if misused
Did relevant content change relative to a comparison base? changes Run docs build when docs changed Unexpected true in no-push contexts; wrong comparison base
Does this project/ref contain a capability/config file? exists Run Node job if package.json exists Cannot see generated artifacts; include lookup context may differ
Should an optional service job run if service exists and changed? Combine exists + changes in one rule Monorepo service Reviewer may miss AND semantics if split across rules

Keywords inside a single rule are combined: all conditions must satisfy that rule. Separate rule entries are alternatives evaluated in sequence. That structural distinction is often clearer than a long Boolean expression.

5. Rule order is precedence: narrow exceptions first

First-match behavior means ordering is not formatting. A broad source rule placed before a narrow when: never exception makes the exception unreachable. Review rule lists top-to-bottom as decision tables, not as unordered sets.

# Risky: broad MR match makes the draft exception unreachable.
rules:
  - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  - if: '$CI_MERGE_REQUEST_DRAFT == "true"'
    when: never

# Better: reject the narrow case first.
rules:
  - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_DRAFT == "true"'
    when: never
  - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

6. Duplicate-pipeline avoidance is a cost and correctness control

When a push updates a branch that has an open merge request, GitLab can have reasons to create both branch and MR pipelines. If job rules also end in broad catch-all behavior, the same SHA can consume twice the runners, publish duplicate reports, race external statuses, or make governance systems pick the wrong pipeline. Prevent duplicates at the pipeline boundary when your delivery model wants only one class.

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push"'
      when: never
    - if: '$CI_COMMIT_BRANCH'
The CI_PIPELINE_SOURCE == "push" guard on the suppression rule matters. Triggered/downstream pipelines can also have a branch; a branch-only suppression could block them unintentionally.

7. compare_to trades implicit context for explicit baseline

rules:changes:compare_to makes the comparison ref reviewable and can stabilize behavior for empty branches or nonstandard contexts. The cost is that the baseline itself becomes configuration state: branch renames, fork behavior, and merged-results pipelines can change what the comparison means. Record the baseline ref and, for release-critical decisions, consider whether a moving branch is sufficient evidence or whether another design is safer.

8. Expression readability and trust

Prefer several small, named pipeline classes to one dense expression. Regex is useful for trusted ref naming conventions, but it is not an authorization system. A branch name matching release/* does not prove the actor is authorized to deploy. Pair conditional inclusion with protected refs/environments, scoped identities, and runner trust where the job has privileged side effects.

Current GitLab rules:if regular expressions use RE2 semantics. Newer GitLab documentation also includes regexp forms for rules:changes/exists; those have different semantics/version history. This chapter’s runnable path uses stable path/glob forms so self-managed learners are not forced onto the newest syntax.

9. Worked scenario: monorepo delivery policy

Decision Choice Prerequisites Affected state Proof
Which pipeline classes exist? MR, default-branch push, nightly schedule Free-compatible workflow rules Pipeline records / compute cost Pipeline source + ID count per SHA
API service tests MR only when services/api/** changes MR diff available Job graph Job present/absent + diff paths
Docs lint Any admitted pipeline when docs tree exists Repository path Job graph exists match + job membership
Nightly dependency audit Schedule only Configured schedule Job graph / runner use source=schedule + job ID
Production deployment Default branch plus separate protected deployment control Protected ref/environment policy where used Privileged side effect Job inclusion + authorization + deployment evidence

10. Availability, portability, and rollback

Core workflow:rules, job rules, if, changes, and exists are part of the ordinary CI/CD configuration surface across GitLab offerings. Advanced surrounding controls—protected environments, policy-injected jobs, merged results/merge trains, enterprise governance—can be tier/offering sensitive. Keep the core rule model free-compatible and label optional controls separately.

Rollback is code rollback: preserve the prior CI configuration SHA, revert the rule change, and verify the resulting pipeline class/job graph. Do not “fix” a rule mistake by manually rerunning arbitrary old jobs if side effects or source context differ.

Knowledge check

You need to stop API pipelines from existing at all. Workflow rule or job rule?

You need tests only when src/ changes in an MR. changes or exists?

Why prefer an explicit final when: never in strict workflow allowlists?

Can a matching release branch regex replace protected deployment authorization?

What is the rollback artifact for a rule design?

Version and compatibility note

GitLab and GitLab Runner evolve continuously. Treat version-sensitive YAML, runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations as assumptions to verify against the current official GitLab documentation before production use. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for any reproducible lab or incident record.

Official references and version notes

Documentation verification date: 2026-09-11. GitLab CI/CD rule semantics evolve; self-managed installations should confirm their deployed GitLab version before relying on newer syntax.

Next lesson

workflow:rules, Job rules, if Expressions, changes, exists, Pipeline Sources, and Conditional Execution: Diagnostics, Failure Modes, Security, and Performance

Use the design principles to diagnose realistic rule failures while preserving pipeline IDs, first-failure graphs, and source/ref evidence.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.