Chapter 12Lesson 01~210 minutes

rules, workflow, Pipeline Sources, Changes, Conditions, and Dynamic Pipeline Creation: Concepts, Architecture, and Mental Model

Understand pipeline creation as a separate control plane from job inclusion: pipeline sources, workflow:rules, job rules, first-match evaluation, source-specific variables, changes, exists, duplicate pipelines, and dynamic child configuration.

workflow:rulesJob rulesPipeline sourcechangesexistsChild pipelines

Learning objectives

  • Explain why pipeline creation and job inclusion are two different decisions.
  • Use CI_PIPELINE_SOURCE and source-specific predefined variables without assuming every variable exists in every pipeline type.
  • Explain first-match rule ordering and how if, changes, and exists compose inside a rule.
  • Predict duplicate branch/MR pipelines caused by overlapping pipeline-creation logic.
  • Distinguish dynamic child configuration generation from ordinary job conditionality and identify its trust boundary.
Availability baseline (verified 2026-08-21). workflow:rules, job rules, rules:if, rules:changes, rules:exists, parent-child pipelines, and dynamic child pipelines are core GitLab CI/CD capabilities on Free, Premium, and Ultimate across GitLab.com, Self-Managed, and Dedicated. Live execution still requires eligible runner capacity; every mandatory exercise therefore has a CI Lint/merged-configuration and prediction path that does not require buying compute. Version-sensitive additions are labeled: directory matching for rules:exists arrived in GitLab 18.2, and rules:changes:regexp is new in GitLab 19.2 and is not required for this chapter.

1. The problem: valid CI can still create the wrong work

Chapter 10 established the pipeline/job execution model and Chapter 11 established the runtime contract inside a job. Chapter 12 moves earlier in time: should GitLab create this pipeline at all, and if it does, which jobs should exist inside it?

This distinction matters operationally. A job that never materializes cannot consume runner capacity, expose a protected variable, call an external service, or confuse reviewers with irrelevant status. Conversely, an overly broad rule can create duplicate pipelines, double cost, and produce two different status stories for the same commit.

2. Two creation gates: pipeline first, jobs second

Creation-time decision flow
flowchart TD
  E[Event / trigger] --> S[CI_PIPELINE_SOURCE + pre-pipeline variables]
  S --> W{workflow:rules}
  W -->|no match / when: never| N[No pipeline object]
  W -->|pipeline allowed| P[Pipeline object]
  P --> R{Each job rules list}
  R -->|first matching rule includes| J[Job object exists]
  R -->|first match excludes / no match| X[Job absent]
  J --> Q[Runner scheduling later]

workflow:rules is evaluated before jobs and decides whether a pipeline object exists. Job rules are then evaluated for each job. Runner selection happens later. A pending job therefore proves the pipeline and job were created; a completely missing pipeline points earlier, at workflow/source/configuration creation.

3. Pipeline source is an event identity, not a branch name

CI_PIPELINE_SOURCE identifies how the pipeline was created. Common values include push, merge_request_event, schedule, web, api, trigger, pipeline, and parent_pipeline. A push source covers both branch and tag pushes, so refine it with branch/tag variables when required.

Source / context What it means Useful discriminator
push Git push created a branch or tag pipeline. CI_COMMIT_BRANCH for branch, CI_COMMIT_TAG for tag.
merge_request_event MR pipeline semantics are enabled for the source branch. CI_MERGE_REQUEST_IID and other MR variables.
web User started a pipeline from the GitLab UI. Source plus selected ref/input values.
schedule A pipeline schedule created the pipeline. Schedule-specific variables/inputs and owner permissions.
api / trigger Pipeline API or trigger-token endpoint created it. Treat supplied inputs/variables as an explicit trust boundary.
parent_pipeline A parent pipeline triggered a child pipeline in the same project. Child jobs should test parent_pipeline, not merge_request_event.

4. Source-specific variables: absence is meaningful

CI_COMMIT_BRANCH exists in branch pipelines but is not available in merge-request or tag pipelines. CI_COMMIT_TAG exists only for tag pipelines. MR-specific variables exist in merge-request pipeline contexts. Treat an absent variable as a different state from an empty business value; write rules around documented availability rather than assuming a universal ref model.

Creation-time constraint: rules are evaluated before jobs run. A dotenv file or shell export created by an earlier job cannot influence whether a later job is added to the already-created pipeline.

5. Job rules are ordered and stop at the first match

GitLab evaluates a job’s rules from top to bottom. The first matching rule determines whether the job is included and can also set attributes such as when or allow_failure. Later rules are unreachable once an earlier rule matches.

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

A scheduled pipeline matches the first rule and excludes the job. An MR pipeline reaches the second rule and includes it. A branch push reaches the third. There is no implicit “continue evaluating after a match.”

6. if + changes + exists inside one rule means AND

Different keywords inside one rule must all match. This is useful when a job should exist only for a particular source and a relevant change set.

docs_check:
  script: echo "docs changed and config exists"
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
      changes:
        - docs/**/*
      exists:
        - docs/config.yml

By contrast, multiple rules are alternatives evaluated in order. Keep that distinction explicit when reviewing YAML.

7. rules:changes depends on a comparison base

For merge-request pipelines, rules:changes compares against the MR target branch. For ordinary branch pipelines it compares with the previous commit. New-branch pipelines and pipeline sources without an associated Git push are the classic trap: without compare_to, changes can evaluate true and include the job even when your mental model expected “no code changed.”

When the source is schedule, tag, or manual/web and file-delta semantics matter, specify rules:changes:compare_to deliberately or choose a different condition.

8. rules:exists checks repository state, not job filesystem state

rules:exists asks whether repository paths exist at configuration-evaluation time. It does not inspect files generated by a job. Job-level rules:exists checks the project/ref running the pipeline. In conditional includes, the search context can instead be the project/ref that contains the include file, which is important for shared templates.

9. How duplicate branch and MR pipelines appear

A push to a branch with an open merge request can satisfy both branch-pipeline and merge-request-pipeline creation paths. A broad final when: always or overlapping job rules can therefore cause simultaneous pipelines for the same change. The preferred fix is usually at workflow:rules: define which pipeline types are allowed, then let job rules specialize within that single pipeline.

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'
    - if: '$CI_COMMIT_TAG' 

10. Dynamic child pipelines create configuration

A dynamic child pipeline is not “a job whose rule is complicated.” One job generates a YAML configuration artifact, and a trigger job asks GitLab to parse that artifact as a new child-pipeline configuration. The child runs in the same project/ref/SHA as the parent, and its jobs see CI_PIPELINE_SOURCE=parent_pipeline.

This is powerful for monorepos and generated matrices, but the generated YAML is executable delivery policy. Never transform unreviewed external or fork-controlled data into privileged child configuration without validation and a clear secret/runner trust model.

11. Read-only inspection before changing rules

  • Record the current .gitlab-ci.yml, includes, and default branch.
  • Inspect Pipeline Editor/CI Lint and merged configuration before triggering anything.
  • List recent pipelines and record source, ref, SHA, status; the source is evidence, not an inference from the branch name.
  • For an MR, inspect whether both a branch and MR pipeline already exist for the same SHA.
  • Record which jobs are absent versus skipped versus manual; these are different states.
  • Do not dump all CI variables into a public log. Print only allowlisted, non-secret metadata needed for the exercise.

12. DevOps connection: non-creation is a security control

Creation-time filtering is part of least privilege. If production deployment, registry publishing, or secret-consuming jobs should not exist for an untrusted source, a rule that prevents their materialization is stronger than creating them and hoping a later script exits early. Deterministic pipeline creation also reduces compute waste and gives reviewers one coherent status surface.

Knowledge check

Which is evaluated first: workflow:rules or a job’s rules?

Why can CI_COMMIT_BRANCH be the wrong test in an MR pipeline?

What happens after the first matching job rule?

Why can rules:changes surprise you in a scheduled or manual pipeline?

What source do jobs in a child pipeline see?

Summary

Pipeline creation is a two-gate control system: workflow:rules decides whether the pipeline exists; ordered job rules decide which jobs exist inside it. Source-specific variables, comparison bases, and generated child configuration are part of the security and correctness model—not syntax trivia.

Official references

Next lesson

Prove branch/MR/change/existence behavior with evidence

Lesson 2 creates a disposable rule matrix, validates it before push, demonstrates an intentionally absent pipeline, and verifies job inclusion from source/SHA/configuration 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
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.