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.
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.
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
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.
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?
workflow:rules. If it prevents pipeline creation, no job rule can resurrect a job because there is no pipeline.
Why can CI_COMMIT_BRANCH be the wrong test in an MR pipeline?
It is not available in merge-request pipelines; use CI_PIPELINE_SOURCE and documented MR variables.
What happens after the first matching job rule?
Evaluation stops. That rule determines inclusion/exclusion and its attributes; later rules are not considered.
Why can rules:changes surprise you in a scheduled or manual pipeline?
There is no associated Git push comparison by default, so changes can evaluate true unless a deliberate compare_to or different condition is used.
What source do jobs in a child pipeline see?
parent_pipeline for a parent-child pipeline, even when the parent itself originated from a merge request.
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
- GitLab Docs — workflow keyword
- GitLab Docs — Specify when jobs run with rules
- GitLab Docs — CI/CD YAML syntax reference
- GitLab Docs — Predefined CI/CD variables
- GitLab Docs — Where variables can be used
- GitLab Docs — Merge request pipelines
- GitLab Docs — Downstream pipelines
- GitLab Docs — Pipeline editor
- GitLab Docs — CI Lint
- GitLab Docs — CI/CD pipelines
- GitLab Docs — Pipelines API
- GitLab Docs — Use CI/CD configuration from other files
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.