GitLab CI’s include keyword lets you avoid repeating yourself by referencing external YAML files, effectively creating reusable CI logic.

Let’s see it in action. Imagine you have a common set of linting and testing jobs that you want to run across multiple projects.

Here’s a templates/.gitlab-ci-template.yml file in a shared repository:

# templates/.gitlab-ci-template.yml
default:
  image: ruby:2.7

stages:
  - lint
  - test

lint:
  stage: lint
  script:
    - gem install --no-document rubocop
    - rubocop --format progress

test:
  stage: test
  script:
    - gem install --no-document bundler --version 2.2.10
    - bundle install
    - bundle exec rspec

Now, in your individual project’s .gitlab-ci.yml, you can include this template:

# .gitlab-ci.yml in a project
include:
  - project: 'shared-repo/ci-templates'
    ref: 'main'
    file: '/templates/.gitlab-ci-template.yml'

rspec_coverage:
  stage: test
  script:
    - gem install --no-document simplecov
    - bundle exec rspec --format progress --coverage
  dependencies:
    - build # Assuming a build job might be needed for dependencies

In this example, the lint and test jobs from the template are automatically added to the pipeline. The rspec_coverage job is added in addition to the template’s test job, demonstrating how you can extend or override template logic. The ref: 'main' specifies the branch to pull the template from, and file points to the exact YAML file within that repository.

The problem this solves is the "copy-paste" maintenance nightmare. Without include, every project needing the same linting or testing setup would require manual duplication of YAML. When the linting rules change, you’d have to update dozens, maybe hundreds, of .gitlab-ci.yml files. With include, you update one file in the shared repository, and all projects referencing it automatically inherit the changes on their next pipeline run. This drastically reduces errors and speeds up updates.

Internally, GitLab CI downloads the referenced YAML file(s) during pipeline creation and merges their contents with the project’s own .gitlab-ci.yml. The merging strategy prioritizes the project’s local configuration, allowing for customization. Jobs defined in the included file are added unless a job with the same name is defined locally, in which case the local definition takes precedence. You can also use include to reference local files within the same repository, or files from other GitLab instances (though this requires more configuration).

The include keyword supports multiple entries, allowing you to combine logic from various sources. You can even include entire directories, which GitLab will process recursively. For complex setups, you can use include to define a base set of jobs and then use other include statements or local definitions to add project-specific variations.

A subtle but powerful aspect is how include handles default keywords. If a default keyword is defined in an included file, and also in the main .gitlab-ci.yml, the last default definition encountered during parsing wins. This means a default defined in your project’s .gitlab-ci.yml will override any default defined in an included template.

The next concept to explore is how to conditionally include CI configurations based on the branch or other pipeline variables.

Want structured learning?

Take the full Gitlab-ci course →