Topic 4.4
Rules, Includes, and Templates
In one line
rules: gives fine-grained control over exactly when a job runs, and include: splits a growing pipeline across multiple files — GitLab's own answers to Phase 1.1's triggers and Phase 2.3's reusable workflows.
Key ideas
- 01
rules:gives fine-grained, CONDITIONAL control over whether a specific job runs at all, beyond just the pipeline-level triggers Phase 1.1 already covered generally — a job can be conditioned on the branch name, whether specific files changed, the pipeline's source (a push versus a merge request versus a schedule), or combinations of these. - 02
rules: - if: '$CI_COMMIT_BRANCH == "main"'runs a job only on themainbranch specifically;rules: - changes: ["src/**/*"]runs a job only if files matching that path actually changed in the triggering commit — genuinely useful for skipping an expensive job (like a full integration test suite) when a change genuinely couldn't have affected the code that job actually tests. - 03
include:lets one.gitlab-ci.ymlpull in configuration DEFINED IN OTHER FILES — either local files within the same repository (splitting one large, unwieldy file into logical, smaller pieces) or, genuinely more powerfully, files from an ENTIRELY DIFFERENT repository, directly the same 'shared, centrally-maintained pipeline logic' benefit as GitHub Actions' reusable workflows (Phase 2.3) or Jenkins' shared libraries (Phase 3.4). - 04
GitLab also provides CI/CD TEMPLATES — genuinely pre-built, ready-to-include configurations for extremely common tasks (security scanning, deploying to specific cloud providers) maintained by GitLab itself —
include: template: Security/SAST.gitlab-ci.ymladds a complete, working static-analysis security scanning job to your pipeline with a SINGLE line, no custom configuration needed to get a genuinely useful baseline running immediately. - 05
Combining
rules:andinclude:lets an organization define ONE shared, centrally-maintained pipeline template (viainclude:from a shared repository) while still letting each individual project's own.gitlab-ci.ymlapply its own specificrules:conditions on top — genuinely powerful together: shared logic, locally-tunable conditions for exactly when that logic actually applies. - 06
This is genuinely the same DRY principle woven through every platform this whole phase has covered — GitHub Actions' reusable workflows, Jenkins' shared libraries, and now GitLab's
include:and templates all exist to solve the identical real organizational problem: define a genuinely good pipeline pattern once, centrally, and let it be reused (and kept consistently updated) everywhere it's needed, rather than being copy-pasted and left to slowly drift apart across every individual repository.
Code & diagrams
Conditional execution and shared, centrally-maintained configuration, in one realistic file.
include:
- local: '.gitlab/ci/build.yml' # split out locally
- project: 'my-org/shared-pipelines' # from another repo
file: '/templates/standard-java-pipeline.yml'
- template: 'Security/SAST.gitlab-ci.yml' # GitLab's own built-in template
integration-tests:
stage: test
script:
- mvn verify -Dtest=IntegrationTests
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"' # only on merge requests
changes:
- "src/main/**/*" # AND only if source actually changed
- if: '$CI_COMMIT_BRANCH == "main"' # OR always on main directly
deploy-production:
stage: deploy
environment: production
script:
- ./deploy.sh production
rules:
- if: '$CI_COMMIT_BRANCH == "main"' # only ever from main
when: manualExplain it without notes
Why would a team configure integration-tests to skip running unless files under src/main/ actually changed, rather than always running it on every single pipeline?
How does GitLab's include: mechanism relate to the reusable-configuration problem Phase 2.3 (GitHub Actions) and Phase 3.4 (Jenkins) each solved with their own platform-specific mechanism?
Practice
Write a rules: condition for a hypothetical job that should only run on merge requests targeting main, and only if files under a specific path changed.
If you have access to a real GitLab project, try including one of GitLab's own built-in CI/CD templates (like the SAST security template) and confirm it adds a working job to your pipeline with minimal configuration.
Trade-offs
- ↔
Rich, conditional
rules:logic gives genuinely precise control over exactly when expensive jobs run, but a pipeline with many layered, interacting rules can become genuinely hard to reason about — predicting exactly which jobs will run for a given specific commit or event sometimes requires carefully tracing through several rules individually, which is worth weighing against simply always running a job (accepting the extra, sometimes-unnecessary cost) when the conditional logic's own complexity starts to outweigh the compute time it's meant to save.
Done when you can
I can write rules: conditions controlling exactly when a job runs, based on branch, source, or changed files.
I can use include: to pull in configuration from a local file, another repository, or a GitLab template.
I understand GitLab's include:, GitHub's reusable workflows, and Jenkins' shared libraries all solve the same underlying reuse problem.