The architecture of modern continuous integration and continuous deployment pipelines necessitates a move away from monolithic configuration files. As projects scale, the .gitlab-ci.yml file often becomes an unmanageable behemoth, leading to configuration drift, merge conflicts, and significant maintenance overhead. GitLab addresses this challenge through the include keyword, a powerful mechanism that allows developers to modularize their pipeline definitions by splitting them across multiple files. This capability transforms the CI configuration from a static script into a dynamic, composable system where common patterns can be reused across different projects, branches, or environments.
The include functionality is not merely a file-concatenation tool; it is a sophisticated merging engine. When GitLab parses a pipeline, it evaluates all included files and merges them into a single effective configuration. This process allows for the centralization of governance—such as security scans or compliance checks—while permitting individual project teams to define specific build and test logic. By leveraging nested includes, templates, and conditional rules, organizations can implement a hierarchical configuration strategy that balances global standardization with local flexibility.
Modularization Strategies via Include Sub-keys
GitLab provides several distinct methods for incorporating external YAML configurations, each tailored to a specific architectural need. These are implemented as sub-keys under the primary include keyword.
Local File Inclusion
The include:local sub-key is the primary method for restructuring a pipeline within a single repository. It allows a developer to reference a file using a relative path from the project root.
- Direct Fact: Use
include:localto reference YAML files located within the same project. - Impact Layer: This enables the separation of concerns. For instance, a project can have a dedicated
ci/build.gitlab-ci.ymlfor compilation and aci/test.gitlab-ci.ymlfor quality assurance, preventing the main.gitlab-ci.ymlfrom becoming thousands of lines long. - Contextual Layer: This is the simplest form of modularization and serves as the foundation for more complex structures, such as the
.gitlab/ci/directory pattern used in Python projects to house common aspects of the CI pipeline definition.
Project-Based Inclusion
When a configuration is useful across multiple projects within the same GitLab instance, include:file (or the project-specific syntax) is employed.
- Direct Fact: This method allows the inclusion of a YAML file from a different project on the same GitLab instance.
- Impact Layer: This is critical for platform engineering teams who maintain "golden paths" or standardized pipeline templates. Instead of copying and pasting YAML across fifty projects, they maintain one central repository of CI logic.
- Contextual Layer: This shifts the maintenance burden from the individual developer to a centralized maintainer, ensuring that a security update to a build script is propagated to all projects simultaneously.
Remote File Inclusion
For organizations operating across multiple GitLab instances or utilizing external configuration sources, include:remote provides the necessary bridge.
- Direct Fact: The
include:remotesub-key fetches a YAML file from an external URL. - Impact Layer: This allows for the sharing of CI logic across entirely different organizational boundaries or different GitLab installations, facilitating a "Configuration as Code" approach that transcends a single instance.
- Contextual Layer: While powerful, this introduces a dependency on external network availability and the security of the remote host.
Template-Based Inclusion
GitLab provides a vast library of sophisticated reference templates that can be integrated using include:template.
- Direct Fact:
include:templateallows the use of official GitLab CI/CD templates or custom-defined templates. - Impact Layer: Noobs and tech enthusiasts can quickly bootstrap their pipelines using industry-standard configurations for languages like Python without writing YAML from scratch.
- Contextual Layer: Users must be aware that official templates are subject to change over time, meaning a pipeline that works today might require updates if the underlying GitLab template is modified.
The Merging Engine and Configuration Overrides
The process of combining the main .gitlab-ci.yml with included files is known as merging. This is not a simple append operation but a logical merge of YAML objects.
Array Merging Limitations
One of the most critical technical constraints of the GitLab merging engine is how it handles arrays, such as the script section of a job.
- Direct Fact: You cannot add or modify individual items within an array during the merge process.
- Impact Layer: If an included template defines a
scriptarray, and the main file defines ascriptarray for the same job, the main file's array completely replaces the template's array. - Contextual Layer: To achieve a "combined" script, the developer must explicitly repeat all previous commands from the template and then add the new command.
Example of Array Overwrite:
If autodevops-template.yml contains:
yaml
production:
stage: production
script:
- install_dependencies
- deploy
And .gitlab-ci.yml contains:
yaml
include: 'autodevops-template.yml'
stages:
- production
production:
script:
- install_dependencies
- deploy
- notify_owner
The resulting job will execute all three commands. However, if the developer only wrote - notify_owner, the install_dependencies and deploy steps would be deleted from the final execution.
Nested Includes and Idempotency
GitLab supports the nesting of includes, meaning an included file can itself include another file.
- Direct Fact: Nested includes can go multiple levels deep (e.g.,
.gitlab-ci.yml->another-config.yml->config-defaults.yml). - Impact Layer: This allows for a layered configuration hierarchy. Global defaults can be placed at the bottom of the chain, while project-specific overrides sit at the top.
- Contextual Layer: GitLab ensures idempotency; if a duplicate configuration file is included multiple times through different paths, it is only processed once, preventing circular dependencies or redundant job definitions.
Dynamic Inclusion via Variables and Rules
Modern GitLab CI versions have introduced the ability to make the include process dynamic, allowing the pipeline to adapt based on the environment or the commit.
Variable Support in Includes
The include section can utilize specific types of variables to determine which files to load.
- Direct Fact: Supported variables include Project, Group, Instance, and Project predefined variables.
- Impact Layer: This allows for environment-specific configuration loading. For example, a different set of CI files can be loaded based on the
$CI_PROJECT_PATH. - Contextual Layer: Starting in GitLab 14.2, the
$CI_COMMIT_REF_NAMEvariable is supported, returning the full ref path (e.g.,refs/heads/branch-name).
Variable Limitations
Not all variables are available during the include phase because YAML files are parsed before the pipeline is actually created.
- Direct Fact: Pipeline predefined variables such as
CI_PIPELINE_ID,CI_PIPELINE_URL,CI_PIPELINE_IID, andCI_PIPELINE_CREATED_ATare unavailable. - Impact Layer: Developers cannot use the unique ID of a pipeline to dynamically select an include file.
- Contextual Layer: Furthermore, variables defined within the
variables:section of a job or the global variables section cannot be used inincludestatements because includes are evaluated before jobs are initialized.
Conditional Inclusion with Rules
The include:rules keyword allows for the conditional inclusion of files based on the state of CI/CD variables.
- Direct Fact:
rules:ifcan be used to conditionally include files. - Impact Layer: This prevents the pipeline from becoming bloated with unnecessary jobs. For example, a "deployment" YAML can be included only if the commit is pushed to the
mainbranch. - Contextual Layer: In versions prior to GitLab 14.5, regex matching for
$CI_COMMIT_REF_NAMErequired the=~operator rather than==.
Troubleshooting Common Inclusion Failures
Despite the power of the include system, developers frequently encounter pitfalls related to stage mapping and job visibility.
The Missing Job Phenomenon
A common issue occurs when a developer includes a file containing multiple jobs, but only the first job appears in the merged YAML view or the pipeline.
- Direct Fact: If the
stageslist in the main file does not match the stages required by the included jobs, those jobs may not execute or appear correctly. - Impact Layer: A developer might define
buildandanother-buildin an included file, but ifanother-buildis not assigned to a stage defined in the main.gitlab-ci.yml'sstageslist, it will be omitted from the pipeline. - Contextual Layer: This often happens when the main file defines a strict list of stages:
yaml stages: <ul> <li>build</li> <li>test</li> <li>deploy<br />
If the included file contains a job that defaults to a stage not in this list, GitLab will ignore the job. To fix this, every included job must be explicitly assigned to a configured stage.
Undefined Needs in Conditional Includes
There is a known limitation regarding the `needs:` keyword when used with conditionally included jobs.- Direct Fact: You cannot use
needs:to create a dependency pointing to a job added viainclude:local:rules. - Impact Layer: When the configuration is validated, GitLab returns
undefined need: <job-name>. - Contextual Layer: This creates a challenge for complex DAG (Directed Acyclic Graph) pipelines where the existence of a dependency is conditional.
Technical Specifications and Version History
The evolution of the `include` keyword reflects GitLab's commitment to expanding the flexibility of CI/CD.| Feature | Version Introduced | Detail |
|---|---|---|
| Initial Include Support | Early Versions | Basic local, remote, and template support. |
| Feature Flag Removal | 13.9 | Standardized the include behavior across all installations. |
| Var Support (Project/Group/Instance) | 14.2 | Allowed dynamic pathing via organizational variables. |
| $CICOMMITREF_NAME Support | 14.2 | Enabled branch-specific inclusion paths. |
| Pipeline Var Support | 14.5 | Expanded variables to include trigger and manual run variables. |
| Regex Resolution | 14.5 | Fixed behavior of $CI_COMMIT_REF_NAME matching in rules. |
| Nested Includes | 14.8 | Allowed configuration files to include other configuration files. |