local, project, remote and template compared
Anyone maintaining a .gitlab-ci.yml in more than one repository knows the problem: build, test and deploy jobs get copied over and over, and slowly drift apart. The include keyword solves exactly this, merging pipeline definitions from other files, other projects, or even external URLs at pipeline compile time. This article walks through all four include variants in detail and shows how they combine into a central CI template library for an entire organization.
Table of Contents
- 1. Why include Is More Than Copy and Paste
- 2. include:local: Splitting One Large Pipeline Into Several Files
- 3. include:project: Pulling Pipeline Building Blocks From Another Repository
- 4. include:remote: External URLs and Their Limits
- 5. include:template: Using GitLab's Built-In Standard Templates
- 6. Building a Central CI Template Library
- 7. Versioning: Why ref Should Never Be main
- 8. Parameterizing With spec:inputs and Combining It With rules
- 9. Best Practices, Pitfalls and a Comparison of All Four Variants
- 10. Summary
- 11. FAQ
1. Why include Is More Than Copy and Paste
In most teams a GitLab CI pipeline starts as a single .gitlab-ci.yml file that grows with the project. As soon as a second or third repository joins, the temptation is to simply copy the working pipeline and adjust the project names. That works short term, but it becomes a maintenance nightmare over time: a change to the Docker registry URL, a new cache key or an extra security scan then has to be applied individually across ten, twenty or a hundred repositories.
include solves this by letting GitLab CI merge job definitions from multiple sources into a single effective pipeline before it runs. The local .gitlab-ci.yml then becomes a thin entry point that references shared building blocks instead of containing every line itself. Four variants are available: local for files in the same repository, project for other repositories on the same GitLab instance, remote for arbitrary HTTPS URLs, and template for GitLab's own built-in templates.
2. include:local: Splitting One Large Pipeline Into Several Files
The simplest form is include:local. It references a file in the same repository and the same Git ref as the calling .gitlab-ci.yml. This is particularly useful once a single pipeline file has grown unwieldy, for example because it holds build, test, security and deploy stages for several microservices in a monorepo. Instead of an 800 line file, several topically separated files emerge, typically under a directory like .gitlab/ci/, each of which is easy to review on its own.
It is important to note that include:local is purely structural and offers no versioning across repository boundaries, because the file and the caller always come from the same commit. That makes it ideal for splitting up a single project but unsuitable when several independent repositories need to share the same logic. For team internal use, though, it is entirely sufficient to significantly improve readability and reviewability without giving up control over the pipeline.
# .gitlab-ci.yml
include:
- local: '.gitlab/ci/build.yml'
- local: '.gitlab/ci/test.yml'
- local: '.gitlab/ci/deploy.yml'
stages:
- build
- test
- deploy
3. include:project: Pulling Pipeline Building Blocks From Another Repository
include:project takes a decisive step further and lets you include a file from a completely different GitLab project, as long as the executing user or runner has read access to the source project. This is exactly the foundation for a central CI library: a dedicated repository, say devops/ci-templates, holds finished jobs for PHP builds, Composer caching, PHPUnit execution or deployment to a specific server type, and every application project includes only what it actually needs.
The big advantage over copying is centralized maintenance: when a job in the library is fixed or extended, for example with a new PHP 8.4 compatibility check, every including project benefits automatically once it updates the referenced ref, or immediately on the next pipeline run if no fixed ref is set. That does carry a risk, though: without deliberate versioning, a change to the library can unintentionally break all dependent pipelines at once, which is why versioning deserves its own section further below.
# .gitlab-ci.yml of an application project
include:
- project: 'devops/ci-templates'
ref: 'v2.4.0'
file:
- '/php/build.yml'
- '/php/phpunit.yml'
- '/deploy/ssh-deploy.yml'
4. include:remote: External URLs and Their Limits
include:remote loads a YAML file from any publicly reachable HTTPS URL. This is useful for pulling in templates from third parties, or for delivering pipeline building blocks through an internal content delivery system that does not live inside GitLab itself. In practice, though, it is the least suitable choice for an internal template library, because GitLab's own authentication does not apply: the URL must be reachable without a GitLab token, which effectively rules out internal, access restricted templates through this route.
Another difference from include:project is the lack of a clean tie to Git refs for versioning. A URL can be parameterized with a tag or commit hash, for instance raw.githubusercontent.com/org/repo/v1.2.0/template.yml, but GitLab itself never verifies that the referenced file belongs to a specific, checkable state. For public, well versioned templates like community security scanners this is acceptable, but for internal company CI building blocks with access restrictions, include:project remains the far more robust and secure choice.
# .gitlab-ci.yml
include:
- remote: 'https://raw.githubusercontent.com/example-org/ci-lib/v1.3.0/php-lint.yml'
5. include:template: Using GitLab's Built-In Standard Templates
include:template pulls in one of the ready made templates that GitLab ships as part of the platform itself, for example for security scanning, Auto DevOps stages or language specific build patterns. These templates live inside GitLab's own gitlab-org/gitlab repository and are updated with every GitLab release without users having to maintain them themselves. They are the fastest way to bring established security or compliance capability into a pipeline without any implementation effort of your own.
The downside is reduced control: when GitLab updates a template, the behavior of your own pipeline can change without any action on your part, unless a fixed GitLab version reference is in place. In practice, many teams combine include:template for GitLab's own standard functions like SAST or dependency scanning with include:project for everything the team develops and versions itself. This combination plays to the strengths of both approaches without giving up either one's flexibility.
# .gitlab-ci.yml
include:
- template: 'Security/SAST.gitlab-ci.yml'
- project: 'devops/ci-templates'
file: '/php/build.yml'
6. Building a Central CI Template Library
A mature template library is a software project in its own right and deserves the same care as application code. A clear directory structure per language or technology makes sense, for example /php, /node, /docker and /deploy, along with a README showing examples of how each building block is included. Every job in the library should be as generic as possible and configurable through variables like APP_NAME or DEPLOY_TARGET, rather than hard wiring project specific assumptions.
For the rollout, a step by step approach is recommended: first fully migrate a pilot project to the library, then let a second and third project follow, and only afterwards tackle migrating every remaining repository. This surfaces teething problems in the templates early, before a hundred projects would be affected simultaneously. A dedicated merge request template inside the library repository that reminds authors to notify all dependent projects of changes further prevents unpleasant surprises from breaking changes.
7. Versioning: Why ref Should Never Be main
The most common mistake when using include:project is omitting the ref parameter or hard coding it to main. That means every pipeline run automatically pulls the very latest state of the library, and a faulty change can instantly take down every dependent project at once. Instead, every application project should reference a fixed tag, say v2.4.0, and update it deliberately and after testing whenever a new version of the library becomes available.
Semantic versioning fits perfectly here: patch releases for bug fixes with no behavior change, minor releases for new, opt in jobs, and major releases for breaking changes such as renamed variables or altered job names. A CHANGELOG.md in the library repository that documents every version makes migration traceable for dependent teams. That way the central library stays a productivity win instead of becoming a source of unpredictable pipeline outages.
8. Parameterizing With spec:inputs and Combining It With rules
Since GitLab 15.11, templates can define typed input parameters at the top of a file through a spec:inputs block, which are passed in at include time. This replaces the previously common and error prone practice of setting variables somewhere else in the pipeline and hoping the included job happens to pick them up. With inputs it is instead explicitly visible which values a template expects, including default values and allowed options.
Combined with rules inside the included jobs, building blocks emerge that behave differently depending on the calling context, for example a deploy job that only actually deploys on the main branch and only performs a dry run on feature branches. This combination of parameterized templates and context dependent rules is exactly what distinguishes a real CI library from a mere collection of copied YAML fragments: it behaves like an actual, configurable software component.
# devops/ci-templates: /deploy/ssh-deploy.yml
spec:
inputs:
environment:
default: 'staging'
options: ['staging', 'production']
---
deploy:
stage: deploy
script:
- echo "Deploying to $[[ inputs.environment ]]"
rules:
- if: '$CI_COMMIT_BRANCH == "main" && $[[ inputs.environment ]] == "production"'
- if: '$CI_COMMIT_BRANCH != "main"'
when: manual
9. Best Practices, Pitfalls and a Comparison of All Four Variants
In practice a clear rule of thumb works well: include:template for anything GitLab offers as a standard feature, include:project for self developed, versioned team building blocks, include:local purely for splitting large files within one repository, and include:remote only for public, externally maintained templates without access restrictions. Mixing all four variants indiscriminately quickly leads to confusion about where a given change actually needs to be made.
A frequent pitfall is circular includes, where two library files reference each other, which GitLab rejects with a clear error message. Equally relevant is the maximum depth of 150 included files per pipeline, which can matter in very large monorepo setups. The table below summarizes the four variants and their key properties to help choose the right method for a given use case.
| Variant | Source | Versioning | Typical Use |
|---|---|---|---|
| include:local | Same repository, same ref | Automatic via commit | Splitting large pipelines into files |
| include:project | Another GitLab project | Via ref (tag/branch/SHA) | Central, team owned CI library |
| include:remote | Any HTTPS URL | Manual via URL path only | Public, externally maintained templates |
| include:template | GitLab standard templates | Tied to GitLab version | Security scans, Auto DevOps building blocks |
Mironsoft
CI/CD pipelines, zero-downtime deployments and release automation
Deployments that run without downtime and without the nail-biting?
We review existing GitLab pipelines for fragile deployment steps and missing safeguards, then build a release process with zero-downtime deployments, automated checks and a rollback you can actually trust in an emergency.
Pipeline Review
Checking an existing .gitlab-ci.yml for fragility, missing stages and security gaps.
Zero-Downtime Deployment
Building symlink releases, health checks and rollback strategies for Magento stores.
CI/CD Automation
Connecting tests, security scans and deployments into one reliable pipeline.
10. Summary
GitLab CI include: Key Takeaways
Four variants
local, project, remote and template cover every use case from splitting files to external templates.
Central library
include:project with a fixed ref turns a dedicated repository into the single source of truth for CI jobs.
Versioning matters
A fixed tag instead of main prevents one library change from breaking every project at once.
Parameterization
spec:inputs turns templates into configurable building blocks instead of rigid copy paste snippets.