The architecture of a GitLab CI/CD pipeline relies on the precise orchestration of jobs, which are the fundamental elements that execute specific commands to accomplish tasks such as building, testing, or deploying code. Central to this orchestration is the ability to control exactly when a job enters a pipeline. Historically, this was managed primarily through the only and except keywords. These keywords allow developers to define the conditions under which a job should be added to a pipeline, such as restricting a deployment job to only run on the main branch or preventing a test job from running during a scheduled pipeline. However, as GitLab has evolved, the only keyword has been superseded by the more powerful and flexible rules keyword. Understanding the transition from only to rules is critical for maintaining modern, scalable pipelines and avoiding the use of deprecated syntax that may lead to future failures or lack of support.
The Mechanics of Job Execution and Control
CI/CD jobs are designed to run on a runner, often within a Docker container, and they operate independently from other jobs within the same stage. Every job generates a comprehensive job log that captures the full execution history, which is essential for debugging and auditing. To manage these jobs, GitLab provides a variety of keywords that define the execution environment and the logic governing the job's lifecycle.
The logic that determines whether a job is added to a pipeline is primarily handled by only, except, or rules. If a job configuration does not explicitly include any of these three keywords, GitLab applies a default behavior where the job is treated as if it has only: branches and only: tags. This means the job will trigger for any branch or tag push by default.
Comprehensive Analysis of the Only Keyword
The only keyword is a job-level configuration used to specify the conditions under which a job is added to a pipeline. It can accept a variety of values, ranging from specific branch names to complex regular expressions and specialized trigger keywords.
Supported Values for Only
The only keyword supports an array of values that dictate the pipeline trigger source:
- branches: This value ensures the job runs when the Git reference for a pipeline is a branch.
- tags: This value triggers the job when the Git reference for a pipeline is a tag.
- triggers: This value is used for pipelines created specifically by using a trigger token.
- web: This value applies to pipelines created manually by selecting New pipeline in the GitLab UI, typically found in the project’s Build > Pipelines section.
- api: This value is utilized for pipelines triggered via the pipelines API.
- chat: This value triggers jobs created by using a GitLab ChatOps command.
- external: This is used when the pipeline is triggered by CI services other than GitLab.
- externalpullrequests: This value is specific to pipelines created when an external pull request on GitHub is created or updated.
- merge_requests: This enables jobs to run when a merge request is created or updated, which is foundational for merge request pipelines, merged results pipelines, and merge trains.
- pipelines: This is used for multi-project pipelines created through the API using
CI_JOB_TOKENor thetriggerkeyword. - pushes: This covers pipelines triggered by a git push event, encompassing both branches and tags.
- schedules: This is used specifically for scheduled pipelines.
Implementation Examples of Only and Except
The only and except keywords can be used to create highly specific execution filters. For instance, a configuration might look like this:
```yaml
job1:
script: echo
only:
- main
- /^issue-.*$/
- merge_requests
job2:
script: echo
except:
- main
- /^stable-branch.*$/
- schedules
```
In this scenario, job1 will only execute if the pipeline is triggered by the main branch, a branch matching the regular expression /^issue-.*$/, or a merge request. Conversely, job2 will run for everything except the main branch, any branch matching /^stable-branch.*$/, or scheduled pipelines.
The Transition from Only to Rules
The only keyword is becoming obsolete in favor of rules. While only is still functional, it lacks the conditional complexity required for modern DevOps workflows. One of the most critical constraints is that only and rules cannot be used together in the same job. Attempting to do so will result in a GitLab error stating that a key may not be used with rules.
Migrating from Only Triggers to Rules
A common requirement is replacing the only: triggers syntax with the modern rules equivalent. The only: triggers keyword is used for pipelines created by trigger tokens. To achieve the same result using rules, developers must leverage the CI_PIPELINE_SOURCE variable.
The correct migration path involves using an if statement to check if the pipeline source is a trigger. The implementation should look like this:
yaml
job_name:
script: echo "Triggered pipeline"
rules:
- if: "$CI_PIPELINE_SOURCE == 'trigger'"
when: on_success
- when: never
In this configuration, the if statement evaluates whether the CI_PIPELINE_SOURCE is equal to trigger. If this evaluates to true, the when: on_success directive is applied, allowing the job to run. If the condition is false, the pipeline falls through to the next rule, which is - when: never. This ensures that the job is strictly excluded from any pipeline not initiated by a trigger token.
Deprecated Variations of Only
Certain uses of the only keyword are now explicitly deprecated. This includes only:variables and except:variables, which were previously used to control job addition based on the status of CI/CD variables. The recommended replacement for these is the rules:if syntax, which provides a more robust way to evaluate variables.
Pipeline Structure: Stages and Job Statuses
The effectiveness of the only and rules keywords is amplified when integrated into a well-structured pipeline using stages. Stages act as the backbone of the pipeline, defining the sequential order of execution.
Defining and Organizing Stages
Stages are declared at the top of the .gitlab-ci.yml file. They create logical groupings for different phases of the software delivery lifecycle:
- Build Stage: Often contains jobs like
build-appandbuild-docs. - Test Stage: Contains quality assurance jobs such as
unit-tests,lint, andsecurity-scan. - Deploy Stage: Contains the final deployment jobs.
The sequence is strict: all jobs in the Build stage must complete before any jobs in the Test stage can begin. However, all jobs within a single stage run in parallel by default, which can significantly reduce the total pipeline duration.
Job Statuses and Monitoring
Once a job is triggered by the conditions defined in only or rules, it enters one of several statuses:
| Status | Description |
|---|---|
| created | The job has been created but not yet processed. |
| pending | The job is in the queue waiting for an available runner. |
| preparing | The runner is preparing the execution environment. |
| running | The job is currently executing on a runner. |
| success | The job completed successfully. |
| failed | The job execution failed. |
| canceled | The job was manually canceled or automatically aborted. |
| canceling | The job is being canceled but the after_script is still running. |
| manual | The job requires a manual action to start. |
| scheduled | The job has been scheduled but execution has not yet started. |
| skipped | The job was skipped due to conditions or dependencies. |
| waitingforcallback | The job is waiting for a callback from an external service. |
| waitingforresource | The job is waiting for resources to become available. |
Advanced Configuration and Deprecations
Beyond the basic only keyword, GitLab CI includes specific configurations for Kubernetes and Pages that have also seen deprecations.
Kubernetes Integration
The only:kubernetes keyword allows a job to run only when the Kubernetes service is active in the project. For example:
yaml
deploy:
only:
kubernetes: active
However, certain sub-configurations under the kubernetes section are now deprecated. Specifically, environment:kubernetes:namespace and environment:kubernetes:flux_resource_path are deprecated when used directly under kubernetes. Users should instead utilize the dashboard settings via environment:kubernetes:dashboard:namespace and environment:kubernetes:dashboard:flux_resource_path.
GitLab Pages and Publish Keywords
The job-level publish keyword and the specific pages job name for GitLab Pages deployment jobs are now deprecated. This is part of a broader effort by GitLab to standardize job naming and deployment triggers.
Comparison of Triggering Mechanisms
The following table summarizes the differences between the legacy only approach and the modern rules approach for common scenarios.
| Scenario | Legacy Syntax (only) | Modern Syntax (rules) |
|---|---|---|
| Trigger Token | only: [triggers] |
rules: [{ if: "$CI_PIPELINE_SOURCE == 'trigger'", when: 'on_success' }] |
| Main Branch | only: [main] |
rules: [{ if: '$CI_COMMIT_BRANCH == "main"' }] |
| Variable Check | only: variables (Deprecated) |
rules: [{ if: '$MY_VAR == "true"' }] |
| Exclude Schedules | except: [schedules] |
rules: [{ if: '$CI_PIPELINE_SOURCE == "schedule"', when: 'never' }] |
Conclusion
The transition from only to rules in GitLab CI represents a shift toward a more programmable and conditional pipeline logic. While only provided a simple way to filter jobs based on branches and tags, it lacked the granularity required for complex CI/CD patterns, such as those involving variable-based triggers or multi-source conditions. The rules keyword allows for a sequential evaluation of conditions, where the first matching rule determines the job's fate, and the when: never directive provides a definitive way to exclude jobs.
For developers maintaining older pipelines, the move toward rules is not merely a matter of syntax but a requirement for future-proofing. The inability to mix only and rules within a single job means that migration must be performed comprehensively. By leveraging the CI_PIPELINE_SOURCE variable, users can replicate every functional aspect of the only keyword—including triggers, web, and api sources—while gaining the ability to use complex logic and variable checks. This evolution ensures that GitLab pipelines remain flexible and capable of handling the demands of modern microservices and high-frequency deployment environments.