GitLab serves as a comprehensive DevOps powerhouse, integrating source code management, continuous integration, and continuous deployment (CI/CD) within a single, unified application. At the core of this ecosystem is the pipeline, a structured sequence of operations designed to automate the building, testing, and deployment of software. While the primary objective of CI/CD is often total automation, there are critical operational scenarios where human intervention is not just beneficial, but mandatory. Whether it is a final sanity check before a production release, a high-cost integration test that should not run on every commit, or a complex deployment workflow requiring executive sign-off, the ability to configure a manual pipeline is a fundamental requirement for professional software delivery.
The distinction between a manual job and a manual pipeline is a common point of confusion for many engineers. A manual job is a specific task within an otherwise automated pipeline that requires a user to click a "play" button to initiate. In contrast, a manual pipeline is a configuration where the entire pipeline creation is restricted, preventing GitLab from automatically triggering a run on every push, and instead requiring a user to manually start the process via the web interface. Understanding these nuances allows teams to optimize resource usage, prevent accidental deployments, and maintain strict governance over their release cycles.
The Structural Components of GitLab CI/CD Pipelines
Before implementing manual triggers, it is essential to understand the hierarchy of a GitLab pipeline. Pipelines are defined in the .gitlab-ci.yml file using YAML keywords and are available across all tiers, including Free, Premium, and Ultimate, and across all offerings such as GitLab.com, GitLab Self-Managed, and GitLab Dedicated.
A pipeline consists of three primary architectural layers:
- Global YAML keywords: These define the overall behavior of the project's pipelines, such as default image settings or global rules.
- Stages: These are the logical groupings of jobs. Stages run in a strict sequential order. For example, a typical pipeline might move from a build stage to a test stage, and finally to a deploy stage. If a job in an early stage fails, the pipeline usually terminates early, preventing subsequent stages from executing.
- Jobs: These are the individual units of work that execute specific commands. Jobs within the same stage run in parallel. A job might be responsible for compiling code, running a linter, or executing a suite of unit tests.
Implementation of Manual Jobs via when: manual
A manual job is a task that is defined within a pipeline but does not execute automatically. This is achieved by applying the when: manual keyword to the job definition.
The impact of using when: manual is that the job will appear in the pipeline graph as "skipped" or "pending" upon the pipeline's start. It remains in this state until a user manually triggers it. This provides a critical safety gate for production deployments, ensuring that code is not pushed to a live environment without a conscious human decision.
Operational Differences: Optional vs. Blocking Manual Jobs
Manual jobs behave differently depending on where the when: manual keyword is placed and the configuration of the allow_failure attribute.
- Optional Manual Jobs: When
when: manualis defined outside ofrules, theallow_failureattribute is set totrueby default. In this configuration, the manual job does not contribute to the overall pipeline status. This means the pipeline can be marked as "passed" even if the manual job has not been executed or has failed. - Blocking Manual Jobs: When
when: manualis defined insiderules, theallow_failureattribute defaults tofalse. These jobs are "blocking," meaning the pipeline will stop at the stage where the job is defined and will not progress to subsequent stages until the manual job is successfully triggered and completed.
Configuring the Pipeline for Manual Trigger Only
A frequent challenge for developers is the "flooding" of the pipelines list. When a pipeline is configured to run on every push, but contains manual jobs, GitLab still creates a pipeline instance for every single commit. This results in a long list of pipelines that are essentially idle, waiting for a human to click a button.
To solve this, the pipeline must be configured to trigger only via the web interface, rather than on every push. This is achieved using workflow:rules.
Utilizing the $CIPIPELINESOURCE Variable
The key to restricting pipeline creation is the $CI_PIPELINE_SOURCE variable. To ensure a pipeline only starts when a user manually clicks "Run pipeline" in the GitLab UI, the following configuration is used:
yaml
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "web"
The value web specifically identifies pipelines created by selecting "New pipeline" in the GitLab UI, located under the project’s Build > Pipelines section.
For more complex scenarios, such as allowing automatic triggers for merge requests and default branches while requiring manual triggers for other branches, a combination of rules is required:
yaml
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
- if: $CI_COMMIT_TAG
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_PIPELINE_SOURCE == 'web'
This configuration creates a dense web of logic where the pipeline is automated for critical paths (tags, main branch, and MRs) but remains manual for feature branches, preventing unnecessary resource consumption.
Step-by-Step Execution for Manual Pipeline Setup
Setting up a manual pipeline requires a precise sequence of actions to ensure the YAML configuration is correctly interpreted by the GitLab runner.
Repository Initialization:
- Log in to the GitLab account.
- Navigate to the "New Project" button to initialize the environment.
- Define the project name and visibility settings.
- Confirm the creation of the project.
Configuration of the .gitlab-ci.yml File:
The.gitlab-ci.ymlfile acts as the blueprint for the entire CI/CD process. For a manual pipeline, the file must define the stages and the specific jobs that require manual intervention.Defining Jobs and Stages:
Jobs must be assigned to specific stages. An example structure would include abuild_jobin the build stage, atest_jobin the test stage, and adeploy_jobin the deploy stage. Each job must contain ascriptsection and thewhen: manualkeyword.Commit and Push:
The configuration file must be committed to the repository. This action triggers GitLab to parse the YAML and prepare the pipeline logic.Execution through the Web Interface:
- Navigate to the "CI / CD" section in the left sidebar.
- Select "Pipelines".
- Click the "Run pipeline" button.
- Once the pipeline is initiated, the user must click the "Play" button next to the specific manual jobs to execute them.
Monitoring and Log Analysis:
Users must monitor the real-time feedback provided by GitLab. By clicking on the individual job, the user can view the execution logs to verify that the script was executed successfully.Managing Dependencies:
If a manual job depends on the completion of a previous job, the sequence of stages must be strictly maintained.
Comparative Analysis of Trigger Methods
The following table delineates the differences between manual jobs and manual pipelines.
| Feature | Manual Job (when: manual) |
Manual Pipeline (workflow: rules) |
|---|---|---|
| Trigger Point | Inside an existing pipeline | At the start of the pipeline creation |
| Pipeline Creation | Automatic (on push/merge) | Manual (via Web UI) |
| Effect on UI | Creates many "skipped" pipelines | Only creates pipelines when requested |
| Primary Use Case | Production deployment gates | High-cost tests, on-demand builds |
| Variable Used | N/A | $CI_PIPELINE_SOURCE == 'web' |
Advanced User-Based Access Control Proposals
In high-security environments, the ability to run a manual job should not be granted to every user with access to the project. There is a documented need to restrict manual actions to specific usernames to prevent unauthorized deployments.
The proposed logic for this restriction involves an only: users block within the job definition:
yaml
deploy_production:
stage: deploy
script:
- echo "Deploy to production"
when: manual
only:
users:
- maintainer2
- maintainer4
This proposal aims to separate user-level permissions from role-level permissions. For instance, while several users may hold the "Maintainer" role, only a specific subset of those maintainers (e.g., maintainer2 and maintainer4) would have the authorization to trigger the production deployment job. The logic suggests that if a general role (like maintainer) is specified alongside specific users, the system should provide a warning that roles may override user-specific restrictions.
Analysis of Manual Job Types and Pipeline Flow
The behavior of a manual job is heavily influenced by its impact on the pipeline's overall status. This is governed by the allow_failure keyword.
In an optional manual job, allow_failure is set to true. This is the standard for jobs defined outside of the rules block. The operational consequence is that the pipeline's health is not dependent on the manual job. If the manual job is never clicked, the pipeline is still considered successful.
In a blocking manual job, allow_failure is set to false. This is the default for jobs defined within rules. The real-world consequence is that the pipeline comes to a complete halt. No further stages can execute until the manual job is triggered. This is essential for critical path deployments where a failure or a lack of approval at the "Deploy to Staging" stage must prevent any code from reaching "Deploy to Production."
Conclusion
The implementation of manual controls within GitLab CI/CD transforms a rigid automation tool into a flexible orchestration platform. By distinguishing between manual jobs and manual pipelines, organizations can eliminate the noise of empty pipeline runs while maintaining strict human oversight over critical deployment phases. The use of $CI_PIPELINE_SOURCE == 'web' within workflow:rules is the definitive method for preventing pipeline flood, ensuring that resources are only consumed when a user explicitly requests a run. Furthermore, the distinction between optional and blocking manual jobs allows architects to design pipelines that are either permissive or restrictive based on the criticality of the stage. As DevOps practices evolve, the movement toward granular, user-based access control for manual triggers represents the next step in securing the software supply chain, ensuring that only authorized personnel can execute the final stages of code delivery.