What is the GitLab CI/CD include keyword and how do you use it to create reusable pipeline templates across projects?
Quick Answer
The include keyword imports external YAML files into your .gitlab-ci.yml, enabling reusable pipeline templates. It supports four sources: local (same repository), file (another project), remote (any URL), and template (GitLab-managed templates). This is how organizations standardize CI/CD across dozens of services.
Detailed Answer
Think of the include keyword like importing building codes in architecture. Instead of every architect writing their own fire safety standards from scratch (duplicating CI/CD configuration), the city publishes a standard building code (a central CI template repository) that every architect references in their blueprints (includes in their .gitlab-ci.yml). Architects can extend or override specific sections for their unique building, but the core safety standards are maintained centrally and updated in one place.
The include keyword in GitLab CI/CD pulls in external YAML files and merges their content with the local .gitlab-ci.yml. It supports four sources. local includes a file from the same repository, useful for splitting a large pipeline into multiple files (like include: local: '/.gitlab-ci/build.yml'). file includes a file from another project in the same GitLab instance, which is the foundation for shared template libraries (like include: project: 'platform/ci-templates' file: '/docker-build.yml'). remote includes a file from any HTTP(S) URL, useful for consuming templates from external sources. template includes GitLab's official managed templates, like the security scanning templates. Multiple includes are specified as a YAML list, and GitLab resolves and merges them in order, with later definitions overriding earlier ones for the same keys.
Internally, when GitLab creates a pipeline, the CI/CD configuration processor first fetches all included files. For local includes, it reads the file from the same commit SHA. For file includes, it reads from the default branch of the target project (or a specified ref). For remote includes, it makes an HTTP GET request with a timeout. All fetched content is merged into a single configuration document using deep merge semantics: arrays are replaced, maps are merged, and later values override earlier ones. The merged configuration is then validated against the CI/CD schema, variables are expanded, and rules are evaluated. GitLab caches fetched includes for the lifetime of the pipeline to avoid redundant network requests. The resolved configuration can be inspected using the CI Lint tool or the merged YAML view in the pipeline editor, which shows the final configuration after all includes have been applied.
In production, a platform engineering team at a company with 50 microservices would maintain a dedicated ci-templates project. This project contains standardized job definitions for common tasks: docker-build.yml defines a kaniko-based image build job, k8s-deploy.yml defines a Helm-based deployment job, security-scan.yml includes all GitLab security scanning templates, and notification.yml sends Slack notifications on pipeline success or failure. Each microservice's .gitlab-ci.yml includes these templates and only provides project-specific variables like the Dockerfile path, Helm chart values file, or Kubernetes namespace. When the platform team improves the build process (adding layer caching or multi-architecture support), they update the template once and every project benefits on its next pipeline run. Version pinning is achieved using the ref keyword to include a specific branch or tag of the template project, allowing teams to upgrade at their own pace.
A critical gotcha is the merge behavior when overriding included jobs. If an included template defines a job with a script of five commands and your local .gitlab-ci.yml redefines the same job with a different script, the local script completely replaces the included script rather than appending to it. Use extends or YAML anchors within templates to create extensible base jobs that consuming projects can inherit from while adding their own steps. Another common issue is circular includes: if template A includes template B which includes template A, GitLab detects the cycle and rejects the configuration. Also, be aware that include: file defaults to the target project's default branch; if you need a specific version, always specify the ref.