The architecture of modern continuous integration and continuous deployment (CI/CD) often requires a surgical approach to job execution. While the primary goal of most pipelines is full automation—triggering on every single git push—there are critical scenarios where automation becomes a liability. Specifically, when dealing with production environments or expensive resource allocations, engineers require a mechanism to ensure that certain stages only execute when a human operator explicitly initiates them via the user interface. This is the functional core of the web-based pipeline trigger in GitLab CI, a configuration that separates automated testing from manual administrative intervention.
By leveraging the .gitlab-ci.yml configuration file, developers can define precise rules that dictate whether a job should be eligible for execution based on the source of the pipeline. The distinction between a pipeline triggered by a git push and one triggered by the "Run Pipeline" button in the GitLab UI is managed through the CI_PIPELINE_SOURCE variable. When this variable is set to web, it signals that the pipeline was initiated manually through the project's Build > Pipelines section. This capability allows for the creation of "administrative" pipelines that do not clutter the standard development cycle but remain available for on-demand execution.
The Anatomy of GitLab CI/CD Pipeline Components
To understand how to isolate jobs to the web interface, one must first understand the structural hierarchy of a GitLab pipeline. A pipeline is not a single entity but a collection of coordinated elements that execute in a specific sequence.
Pipelines are fundamentally composed of three layers:
- Global YAML keywords: These provide the overarching control for the project's pipelines, defining the environment and behavior that apply across all jobs.
- Stages: These act as logical groupings for jobs. Stages run in a strict sequence. For example, a pipeline may have a
buildstage, followed by ateststage, and finally adeploystage. Within a single stage, all jobs run in parallel. A pipeline only moves to the next stage if all jobs in the current stage complete successfully. - Jobs: These are the smallest units of execution. A job is a set of instructions executed by a GitLab Runner, often within a Docker container. Each job produces a unique execution log that provides a full audit trail of the commands run.
The interaction between these components determines the flow of execution. For instance, in a standard three-stage pipeline, a compile job in the build stage must succeed before test1 and test2 in the test stage can begin. Subsequently, a deploy-to-production job in the deploy stage will only execute if all preceding test jobs have successfully concluded. This sequential dependency is what makes the web trigger so powerful, as it allows an operator to stop a pipeline in a "blocked" state before the final, high-risk deployment stage occurs.
Implementing Web-Only Triggers via CIPIPELINESOURCE
The most effective way to ensure a job runs only when triggered via the GitLab UI is by using the rules keyword combined with the CI_PIPELINE_SOURCE predefined variable.
The CI_PIPELINE_SOURCE variable identifies the event that triggered the pipeline. When a user navigates to CI/CD -> Pipelines and clicks the "Run Pipeline" button, GitLab assigns the value web to this variable.
To implement this, the .gitlab-ci.yml must be configured as follows:
yaml
plan_prod:
stage: plan prod
rules:
- if: '$CI_PIPELINE_SOURCE == "web"'
script:
- TS_ENV=prod terraspace plan demo
In this configuration, the plan_prod job will be completely ignored by the pipeline if the trigger was a git push, a merge request, or a schedule. It only becomes part of the pipeline graph when the "web" source is detected. This prevents production planning jobs from running and consuming runner resources on every minor commit.
Manual Approvals and the Blocked State
While the web trigger controls whether a job enters the pipeline, the when: manual keyword controls whether that job starts automatically once it is in the pipeline. These two concepts are often confused but serve different purposes in a production workflow.
The when: manual attribute is used to create a "manual approval" gate. When a job is marked as manual, the pipeline does not execute that job automatically. Instead, the pipeline enters a "blocked" status. The user must then manually interact with the GitLab UI to trigger the job.
A comprehensive example of combining web-only triggers with manual approvals is seen in the following configuration:
yaml
up_prod:
stage: up prod
rules:
- if: '$CI_PIPELINE_SOURCE == "web"'
when: manual
script:
- TS_ENV=prod terraspace up demo -y
In this scenario, two layers of security are applied:
1. The job only exists in the pipeline if the pipeline was started via the Web UI.
2. Even after the pipeline starts, the job will not execute until a user clicks the "Play" button or selects "Run" from the job panel.
The real-world impact of this setup is a secure deployment gate. A lead engineer can start a pipeline via the web interface, review the logs of the preceding plan stage to ensure the infrastructure changes are correct, and only then click the play button to apply the changes to the production environment.
Navigating the GitLab UI for Manual Execution
To execute a pipeline configured for web-only triggers, the operator must follow a specific sequence within the GitLab interface:
- Navigate to the left-side menu and select CI/CD -> Pipelines.
- Locate the "Run Pipeline" button in the upper right-hand corner.
- If required, specify any custom variables; otherwise, leave the defaults as is.
- Click "Run Pipeline" to initiate the build.
Once the pipeline is active, the behavior is as follows:
- Jobs without
when: manual(but with thewebrule) will execute immediately. - The operator can click on each stage to view the logs and verify the output (such as a
terraspace planpreview). - When the pipeline reaches a job marked as
when: manual, the overall pipeline status changes to "blocked". - To resume the pipeline, the operator clicks the "Play" button associated with the blocked job, which transitions the pipeline back into a "running" state.
Distinguishing Web Triggers from Other Pipeline Sources
It is critical for DevOps engineers to understand that web is just one of many possible pipeline sources. Misconfiguring these can lead to "pipeline flooding," where the pipelines list is overwhelmed by unintended runs.
The following table details the various CI_PIPELINE_SOURCE values and their triggers:
| Source Value | Triggering Event |
|---|---|
web |
Created by selecting "New pipeline" in the GitLab UI (Build > Pipelines) |
push |
Triggered by a Git push event, including branches and tags |
merge_request_event |
Created when a merge request is updated or created |
schedule |
Triggered by a pre-defined pipeline schedule |
trigger |
Created via a trigger token (API) |
parent_pipeline |
Triggered by a parent pipeline in a child pipeline setup |
pipeline |
Used for multi-project pipelines |
webide |
Created via the GitLab Web IDE |
external |
Triggered by services other than GitLab |
external_pull_request_event |
Triggered by an external GitHub pull request |
ondemand_dast_scan |
Specific to on-demand DAST scans |
ondemand_dast_validation |
Specific to on-demand DAST validation |
security_orchestration_policy |
Triggered by scheduled scan execution policies |
By exclusively using if: $CI_PIPELINE_SOURCE == "web", the developer ensures that the job is isolated from all other event types, effectively turning the job into a tool that can only be wielded by a human operator via the browser.
Advanced Filtering and Instance-Specific Execution
In complex enterprise environments, a single repository may be mirrored across multiple GitLab instances (e.g., a public GitLab.com instance and a private self-managed instance). In such cases, simply restricting a job to the web trigger may not be enough, as a user might trigger a web pipeline on the wrong instance.
To restrict job execution to a specific GitLab instance, engineers can use the CI_SERVER_HOST variable. This variable identifies the hostname of the GitLab instance running the pipeline.
For example, if a release stage should only run on a specific internal instance for tags following a PEP-440 versioning pattern, the configuration would look like this:
yaml
release_job:
only:
refs:
- /^v\d+\.\d+\.\d+([abc]\d*)?$/
variables:
- $CI_SERVER_HOST == "gitlab.example.org"
script:
- echo "Releasing on the authorized instance"
This dual-layer filtering (checking both the Git ref and the server host) prevents accidental releases from unauthorized or secondary GitLab instances, ensuring that the deployment logic is only executed where it is intended.
Pipeline Types and Dependency Management
The use of web triggers often intersects with different pipeline architectures. Depending on the complexity of the project, a "web" pipeline might be a basic pipeline or a more advanced structure.
- Basic Pipelines: These run everything in each stage concurrently, moving to the next stage only after all jobs in the current stage succeed.
- Needs-Based Pipelines: By using the
needskeyword, jobs can run based on specific dependencies rather than waiting for an entire stage to finish. This allows awebtriggered job to start as soon as its specific prerequisite is met, regardless of other jobs in the same stage. - Parent-Child Pipelines: Large projects may use a parent pipeline to trigger multiple child sub-pipelines. In this architecture, a web trigger at the parent level can cascade down to child pipelines, which must also be configured to handle the
parent_pipelinesource if they are to be triggered this way. - Merge Request Pipelines: These are specifically for merge requests. Using a
webtrigger allows an operator to run a "manual" version of a merge request pipeline for testing before the actual MR pipeline is triggered by a commit.
Analysis of Manual Triggering Strategies
The distinction between rules: - if: $CI_PIPELINE_SOURCE == "web" and when: manual is a common point of contention in GitLab CI configuration.
If a user only uses when: manual, the job will appear in every single pipeline (including every single push). While the job won't run automatically, it still exists in the pipeline graph. For many teams, this leads to "pipeline clutter," where the pipeline view is filled with manual jobs that are irrelevant to the current commit.
By contrast, using the web source check removes the job from the pipeline entirely unless the pipeline is started via the UI. This results in a cleaner pipeline graph for developers while maintaining a powerful "administrative" interface for operators.
The most robust architecture for production deployment follows this logic:
1. Use rules: if $CI_PIPELINE_SOURCE == "web" to ensure the job only exists during manual UI-triggered runs.
2. Use when: manual to ensure that even within that UI-triggered run, the final "apply" or "deploy" step requires a second, explicit confirmation.
This creates a two-factor authorization process for infrastructure changes: first, the decision to start the administrative pipeline, and second, the decision to execute the final deployment job after reviewing the logs of the preceding plan stage.