skip to content

In a GitLab CI .gitlab-ci.yml, what does the include: keyword pull in, and how do its local, project, remote and template forms differ?

level: juniorimportance: must knowfreq 68%

answer

  1. four sources of shared configuration
  2. same repo, other project, URL, GitLab-shipped
  3. merged at pipeline creation, not runtime
  4. ref: decides which version you get
  5. your own keys override the included ones

basics

~20 s

GitLab CI's include merges other YAML configuration into your pipeline when the pipeline is created. local reads a file from the same repository and commit, project a file from another project on the same instance, remote a publicly reachable URL, and template a file GitLab ships with the instance.

solid answer

~50 s

`include:` pulls external YAML into the pipeline configuration, so shared jobs live in one place instead of being copy-pasted into every repository. There are four classic forms. `include:local` takes a path in the same repository, read at the **same commit** as the pipeline, so a branch sees its own version of the shared file. `include:project` takes `file:` from another project on the same GitLab instance, with an optional `ref:` — if you omit it you get the default branch's HEAD, which means the include can change under you. `include:remote` fetches a full HTTP(S) URL with an unauthenticated GET, so the file has to be publicly readable. `include:template` names one of the templates bundled with GitLab, such as `Jobs/SAST.gitlab-ci.yml`. Newer GitLab versions add a fifth form, `include:component`. Everything is resolved at pipeline creation into one merged configuration, and a job key you define locally overrides the same key from an included file.

go deeper

for a junior

Know that include: brings shared YAML into your pipeline and be able to name the four sources: a file in this repo, a file in another project, a URL, and a template GitLab ships. Say that it happens when the pipeline is created.

for a middle

Explain the merge: files are combined in order, your own .gitlab-ci.yml applies last, and a locally redefined job overrides only the keys it names. Be ready to say what ref: defaults to and why that matters.

for a senior

Show the operational judgment: pin template refs for reproducibility, prefer include:project over include:remote on your own instance, and diagnose configuration errors as pipeline-creation failures rather than job failures.

for a principal

Own the versioning contract of a shared template repository — how consumers pin, how you roll a breaking change, and whether you standardise on include:project with tags or on published catalog components with typed inputs.

## The problem `include` solves A platform team that owns build, test and deploy conventions does not want fifty repositories each carrying its own hand-edited copy of the same pipeline. `include:` is GitLab CI's answer: your `.gitlab-ci.yml` names other YAML files, GitLab fetches them while it is creating the pipeline, and the result is one merged configuration that the pipeline actually runs. The merge happens **once, at pipeline creation**. By the time jobs start, there is no trace of which file a job came from — GitLab has already flattened everything. That single fact explains most of the behaviour below. ## The four classic forms ### `include:local` ```yaml include: - local: '/ci/build.yml' ``` A path inside the same repository, always read from the **same commit that triggered the pipeline**. A merge request that edits `/ci/build.yml` therefore tests its own edited version — which is exactly what you want for reviewing pipeline changes. The path is absolute from the repository root; a leading slash is conventional. ### `include:project` ```yaml include: - project: 'platform/ci-templates' ref: 'v2.3.0' file: - '/build.yml' - '/deploy.yml' ``` A file from a **different project on the same GitLab instance**. `file:` accepts a list, so one entry can pull several files. `ref:` is the important one: omit it and GitLab uses the default branch's current HEAD, so someone merging to that template repository's default branch silently changes every consumer's next pipeline. Pin `ref:` to a tag when you want reproducibility, and leave it floating only when you deliberately want fleet-wide rollout. Access is checked against the user running the pipeline, so this form works for private projects — no extra credential to manage. ### `include:remote` ```yaml include: - remote: 'https://example.com/ci/build.yml' ``` A full URL fetched over HTTP(S) with a plain GET at pipeline creation. GitLab sends no credentials, so the file must be publicly readable, and whatever is served at that instant becomes part of your configuration. That is a supply-chain surface, and it is why most teams prefer `project` on their own instance. ### `include:template` ```yaml include: - template: 'Jobs/SAST.gitlab-ci.yml' ``` One of the templates shipped with the GitLab instance itself — the SAST, dependency-scanning and Auto DevOps building blocks. You do not host these; they come with the version of GitLab you are running, so an upgrade can change them. ### The fifth form: `include:component` Recent GitLab versions add `include:component`, which references a versioned CI/CD component published to the catalog and passes it typed `inputs:`. It is the modern replacement for pointing `include:project` at a template file and configuring it through global variables. ## Merge semantics you must know Included files are merged **in the order listed**, and your own `.gitlab-ci.yml` is applied last. If an included file defines a job named `build` and you also define `build`, the two are merged key by key and your local keys win — so overriding just the `image:` of a shared job is a three-line local block, not a fork of the template. ```yaml include: - project: 'platform/ci-templates' ref: 'v2.3.0' file: '/build.yml' build: # same job name as in the template image: node:22 # overrides only this key ``` Includes can nest — an included file may itself include others — and GitLab enforces a documented limit on how many files one pipeline may pull in, so deep template chains eventually fail at pipeline creation rather than at runtime. Duplicate includes of the same file are de-duplicated. ## Where it goes wrong The two classic failures are both about *when* things are read. First, an unpinned `include:project` means your pipeline configuration changed because someone else merged something — the commit that "broke the build" is not in your repository at all. Second, people expect `include` to be a runtime import and try to make the included file depend on job output; it cannot, because the merge is finished before the first job starts. Anything dynamic has to be expressed as `rules:` on the merged jobs, or as a child pipeline generated from an artifact. A third, quieter failure: an `include:` error is a **pipeline creation** error. The pipeline does not run at all and there are no red jobs to look at — you get a configuration error on the pipeline page, which is why `Validate` / the CI lint tool (with "include" expansion) is the right debugging tool.

  • If you include a file from another project without specifying ref:, what version do you get?
    The file as it exists on that project's default branch HEAD at the moment your pipeline is created. That makes every consumer track the template repository's default branch, so a merge there changes your next pipeline with no commit in your repository. Pin `ref:` to a tag or a commit when you need reproducible pipelines, and leave it floating only when fleet-wide rollout is the intent.
  • An included file defines a job you want to keep but with a different image. Do you have to copy the whole job?
    No. Redefine the job by name in your own `.gitlab-ci.yml` and set only `image:`. Included configuration and local configuration are merged per job key, with local keys winning, so the template's `script`, `stage` and `rules` survive. Copying the whole job is the anti-pattern — it silently stops tracking template updates.
  • Where does an invalid include show up — as a failed job or somewhere else?
    Neither the job nor any job runs: include resolution happens while GitLab is creating the pipeline, so a missing file, an unreachable URL or a permissions failure surfaces as a pipeline configuration error with no jobs at all. Debug it with the CI lint / pipeline editor validation, which can show the fully expanded merged configuration.

saying these in an interview costs you the question

  • Thinking include imports files at job runtime
  • Assuming include:project always tracks a fixed version
  • Believing include:remote sends your credentials to the URL
  • Copying whole jobs instead of overriding single keys
  • Confusing include:template with your own template files

context