Reusable GitLab Includes and Components
Intro: Shared pipeline logic is powerful because it eliminates drift. It is also risky because it creates a dependency chain that can silently change how dozens of pipelines behave. Treat reusable pipeline configuration the way you treat shared libraries: version it, review it, and pin it intentionally.
What this page includes
- how
includeand components fit into GitLab pipeline design- examples of reusable security scanner jobs
- safer consumption patterns for platform-owned pipeline building blocks
- comments on pinning, trust, secrets, and runner isolation
Working assumptions
- shared pipeline code should reduce duplication without hiding security logic from the teams that consume it
Reuse options at a glance
| Mechanism | Best use | Security note |
|---|---|---|
include:local |
reuse within the same repo | easiest to review in one MR |
include:project |
central platform-owned templates | protect the source project and ref |
include:remote |
public external YAML | highest integrity risk; prefer pinned or internally mirrored sources |
include:template |
GitLab-provided templates | review before adopting blindly |
include:component |
versioned reusable catalog items | pin to a specific version or SHA-like stable reference |
Example: local include for shared scanner jobs
Root .gitlab-ci.yml:
include:
- local: .gitlab/ci/security.yml
stages:
- build
- security
- deploy
.gitlab/ci/security.yml:
semgrep_scan:
stage: security
image: semgrep/semgrep:latest
script:
- semgrep scan --config p/default --json --output semgrep.json
artifacts:
paths: [semgrep.json]
bandit_scan:
stage: security
image: python:3.12-alpine
script:
- pip install bandit
- bandit -r app -f json -o bandit.json
artifacts:
paths: [bandit.json]
Local include is simple and transparent. Use it first before building a central platform abstraction.
Example: project include for a platform-managed gate
include:
- project: platform/ci-templates
ref: v2.3.1
file:
- /security/common-gates.yml
- /security/release-evidence.yml
Why it is useful:
- one team maintains common patterns centrally;
- consuming repos pin to an explicit version;
- upgrades can be tested and rolled out deliberately.
Example: component include with inputs
include:
- component: $CI_SERVER_FQDN/platform/security-components/semgrep-gate@1.4.0
inputs:
stage: security
config: p/default
fail_on_severity: high
Components are useful when you want a reusable unit with parameters instead of a monolithic shared YAML file.
Security review checklist for reused pipeline logic
Before consuming a shared include or component, check:
- where the source repo lives and who can change it;
- whether you pin a stable version;
- whether the included jobs request secrets, tokens, or privileged runners;
- whether the included jobs publish artifacts externally;
- whether any remote scripts or images are pulled at runtime;
- whether the component changes merge behavior, deploy behavior, or environment targeting.
Example: a reusable Semgrep component contract
Consumer-side usage:
include:
- project: platform/security-components
ref: v1.0.4
file: /components/semgrep.yml
variables:
SEMGREP_RULESET: "p/default"
semgrep_gate:
extends: .platform_semgrep_gate
Platform-side component:
.platform_semgrep_gate:
stage: security
image: semgrep/semgrep:1.84.0
script:
- semgrep scan --config "${SEMGREP_RULESET:-p/default}" --json --output semgrep.json
artifacts:
paths: [semgrep.json]
expire_in: 14 days
This keeps the consumer YAML thin without making behavior mysterious.
Conditional include example
include:
- local: .gitlab/ci/python-security.yml
rules:
- exists:
- requirements.txt
- pyproject.toml
This lets a monorepo or mixed-codebase project include only the relevant pipeline logic.
Safe versioning guidance
Prefer:
- exact release tags for internal, trusted component projects;
- commit-SHA-style pinning when the source is especially sensitive;
- explicit upgrade MRs rather than silent drift.
Avoid:
latest;- remote includes from uncontrolled locations;
- hidden dependency changes that alter release or security semantics.
Secret and token discipline
A shared component should not assume broad secret access.
Safer patterns:
- require inputs rather than hardcoding environment names;
- scope variables to protected refs and environments;
- keep deploy credentials outside generic scanner components;
- document required runner tags and environment expectations.
Example: release evidence component pack
include:
- project: platform/security-components
ref: v1.2.0
file: /components/release-evidence.yml
release_evidence:
extends: .platform_release_evidence
variables:
EVIDENCE_FILES: "semgrep.json,bandit.json,dependency-check-report.xml,zap-report.json"
The component can standardize packaging and evidence collection while each repo decides which reports to produce.
Component governance model
A healthy ownership model usually includes:
- protected default and release branches in the component project;
- mandatory merge request approvals;
- signed commits or at least stricter provenance checks;
- changelog entries for user-facing behavior changes;
- a test project that validates the component before release.
Cross-links
- GitLab CI YAML Deep Dive
- Runner Isolation and Trust Boundaries
- Gate Aggregation Scripts
- GitLab Release Evidence