Chapter 12Lesson 03~205 minutes

rules, workflow, Pipeline Sources, Changes, Conditions, and Dynamic Pipeline Creation: Configuration, Design Choices, and Tradeoffs

Choose deterministic workflow and job-rule designs by balancing suppression scope, change-comparison semantics, explicit positive rules, static configuration, and dynamic child-pipeline complexity.

Rule designcompare_toDuplicate avoidanceStatic vs dynamicGovernanceCost

Learning objectives

  • Choose workflow-level suppression only when the entire pipeline should not exist.
  • Choose job-level rules when the pipeline remains useful but one job is irrelevant.
  • Use rules:changes with an explicit understanding of source-specific comparison semantics.
  • Avoid broad catch-all rules that create duplicate or future-unrecognized pipeline types.
  • Evaluate when dynamic child pipelines improve architecture and when they merely add a second configuration compiler.
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. Design principle: place policy at the correct creation layer

Rule design is easier when the policy sentence has a clear subject. “No pipeline should exist for draft MRs” belongs at workflow:rules. “Run docs validation only when docs change” belongs on the docs job. Using a job rule to imitate whole-pipeline suppression leaves an empty or misleading pipeline; using workflow to suppress a pipeline just because one expensive job is irrelevant can remove useful fast feedback.

2. Workflow suppression versus per-job exclusion

Decision Prefer workflow:rules Prefer job rules
Scope The entire pipeline type/event is disallowed or redundant. Pipeline should exist, but this job is conditional.
Security No jobs at all should materialize for this source. Only one sensitive/expensive job must be absent.
Cost All jobs would be waste for this source. Keep cheap checks; suppress only expensive work.
Observability Absence of pipeline is the intended signal. A pipeline should still provide status for other checks.
Maintenance One centralized pipeline-type contract. Policy stays close to the job it governs.

3. rules:changes: efficient when the comparison is well-defined

Path filtering can save substantial compute in monorepos, but only when the comparison base matches the event. MR pipelines naturally compare with the target branch; branch pipelines compare with the previous commit. New branches and sources without a push require extra care. If a scheduled pipeline should run tests based on the current branch versus main, use compare_to explicitly rather than assuming the last push supplies a baseline.

backend_tests:
  script: ./test-backend.sh
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
      changes:
        - backend/**/*
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
      changes:
        paths:
          - backend/**/*
        compare_to: 'refs/heads/main' 

4. Explicit positive rules versus broad catch-all fallbacks

An explicit allow-list makes newly introduced pipeline sources default to “not included” until reviewed. A final when: always is convenient but broad: it may include the job in scheduled, API, trigger, parent, policy-driven, or future pipeline sources you never intended.

Pattern Strength Risk
Explicit source rules Readable, deterministic, least-privilege. Must be updated when a new legitimate source is added.
Final when: always Convenient default. Can create duplicate pipelines/jobs and silently opt into new sources.
Negative list + final allow Useful when policy truly is “everything except X.” Future sources are automatically allowed; requires governance review.

5. Rule order is part of policy, not formatting

Because the first match wins, putting a broad branch rule above a special default-branch rule can make the special case unreachable. Review rules like firewall policy: from specific deny/override cases to intended general cases, with comments where order is security-significant.

# Wrong for a special default-branch manual gate:
rules:
  - if: '$CI_COMMIT_BRANCH'                  # catches main first
  - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
    when: manual

# Better:
rules:
  - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
    when: manual
  - if: '$CI_COMMIT_BRANCH' 

6. Prefer source identity over variable side effects

Do not use presence of a loosely related variable as a proxy for pipeline type if CI_PIPELINE_SOURCE expresses the intent directly. Source-specific variables then refine the case. This makes the configuration more compatible with web/API/schedule/child contexts and easier to diagnose from pipeline metadata.

7. Static configuration versus dynamic child pipelines

Static YAML is easier to review, lint, cache mentally, and audit. Dynamic child configuration can be valuable when the job graph itself is data-dependent—for example, generating component-specific pipelines in a large monorepo. But it creates another compiler step: generator inputs → generated YAML artifact → GitLab parser → child jobs.

Criterion Static configuration Dynamic child configuration
Reviewability Direct repository diff. Review generator plus generated output/evidence.
Flexibility Best when topology is known. Best when topology depends on repository data/change sets.
Trust No generated-code boundary. Generated YAML becomes executable CI policy.
Failure modes Main config parse/rule failures. Generator, artifact, child config parse, trigger, child jobs.
Cost/complexity Lower. Higher; can reduce monorepo job count when designed well.

8. Dynamic configuration is a code-generation trust boundary

If a generator reads untrusted branch content and emits YAML that can request privileged runners, protected environments, or secret-consuming jobs, the generator has become a policy compiler. Constrain the inputs, validate the generated file, use least-privilege runners/variables, and keep target-project privileged resources inaccessible to untrusted fork pipelines.

9. Offering, tier, and version boundaries

  • Free path: all mandatory rule/workflow/change/existence examples and parent-child/dynamic child concepts are available on Free across GitLab.com, Self-Managed, and Dedicated.
  • Runner compute: feature availability does not guarantee free hosted execution capacity; use CI Lint and no-runner reasoning when needed.
  • GitLab 18.2+: directory paths are supported by rules:exists. Use file paths for older installations.
  • GitLab 19.2: rules:changes:regexp is new. This chapter uses established glob/path behavior so older supported installations can follow the mandatory path.
  • Self-Managed: verify instance version before adopting newer rule syntax or relying on SaaS-current behavior.

10. Worked decision table: monorepo delivery policy

Scenario: a monorepo has fast linting, backend tests, frontend tests, and a nightly full test. Which control belongs where?

Requirement Recommended control Why
Avoid branch + MR duplicates workflow:rules with MR preference. This is whole-pipeline creation policy.
Backend tests only when backend changes Job rules:changes. Only one job is conditional.
Nightly full test regardless of changed files Schedule source rule without path filtering. Schedule intent is time-based, not push-delta based.
Hundreds of independent components discovered at runtime Consider generated child pipelines. Dynamic topology may reduce unnecessary jobs, at added complexity.
Production deployment never exists for fork/untrusted MR context Creation-time job/workflow exclusion plus protected-resource policy. Prevents privileged job materialization; later chapters deepen secrets/environments.

11. Maintainability, reliability, performance, and cost

Every extra rule branch expands the state space you must test. Prefer small source matrices, centralize whole-pipeline decisions, and keep job-specific conditions local. Path filters can save compute but become a correctness risk when comparison semantics are misunderstood. Dynamic children can reduce monorepo fan-out but add artifact and parsing latency. Optimize only after the deterministic state model is testable.

Knowledge check

Should “skip only docs_test when docs did not change” normally be workflow or job rules?

Why can a final when: always be a governance risk?

When is compare_to most valuable?

What extra trust boundary does dynamic YAML introduce?

Why is rule order a design concern?

Summary

Good rule architecture is intentionally boring: a small pipeline-source contract at workflow level, local job predicates for job-specific work, explicit comparison semantics, minimal fallbacks, and dynamic configuration only when it solves a real topology problem. This reduces both runner waste and policy ambiguity.

Official references

Next lesson

Diagnose creation failures without guessing

Lesson 4 uses pipeline source, resolved configuration, rule order, comparison base, and generated-artifact evidence to repair duplicate, missing, and invalid pipelines at the correct layer.

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.