The implementation of manual deployments within GitLab CI/CD represents a critical intersection between automated continuous integration and human-governed delivery. In a standard CI/CD pipeline, the objective is often the total removal of manual intervention to achieve high-velocity releases. However, in enterprise environments, the necessity for a "human-in-the-loop" remains paramount, particularly when transitioning code from staging or pre-production environments into a live production ecosystem. Manual deployment jobs allow organizations to maintain a rigorous quality gate, ensuring that a qualified operator has verified the state of the application and the readiness of the infrastructure before triggering the final release. This mechanism transforms a fully automated pipeline into a semi-automated workflow where the execution of specific deployment logic is deferred until an explicit signal is provided via the GitLab User Interface.
The Architecture of Manual Jobs in GitLab CI/CD
The foundational component of any GitLab CI/CD pipeline is the .gitlab-ci.yml file. This YAML configuration defines the global behavior of the project's pipelines, the stages of execution, and the individual jobs that carry out specific tasks. Within this structure, a manual job is defined by the when: manual keyword. This instruction tells the GitLab runner that the job must not start automatically upon the creation of the pipeline; instead, it must wait for a user to trigger it.
By default, when a pipeline starts, manual jobs are displayed as skipped. This ensures that the pipeline's visual flow does not falsely indicate a failure or a pending state for jobs that are intentionally designed to be triggered by a human. The ability to define manual jobs is available across all GitLab tiers, including Free, Premium, and Ultimate, and is supported on GitLab.com, GitLab Self-Managed, and GitLab Dedicated offerings.
The distinction between different types of manual jobs is critical for pipeline flow and status reporting:
- Optional Manual Jobs: These occur when
allow_failureis set to true. This is the default behavior for jobs defined withwhen: manualoutside of arulesblock. In this configuration, the job's status does not impact the overall pipeline result. A pipeline can be marked as "passed" even if the manual deployment job remains unexecuted. - Blocking Manual Jobs: These occur when
allow_failureis set to false. This is the default behavior for manual jobs defined inside arulesblock. A blocking manual job halts the pipeline progress at the stage where it is defined. The pipeline will not proceed to subsequent stages until the manual job is either triggered and succeeds or is manually skipped.
Configuring Manual Deployments for Production
To implement a manual deployment, the .gitlab-ci.yml file must be configured with a job that specifies the environment, the script to be executed, and the conditions under which the manual trigger is available.
The following configuration exemplifies a production deployment job:
yaml
deploy_prod:
stage: deploy
script:
- echo "Deploy to production server"
environment:
name: production
url: https://example.com
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual
In this configuration, the when: manual keyword exposes a "Run" button in the GitLab UI. The interface provides the text "Can be manually deployed to production," signaling to the operator that the environment is ready for a release. The rules section ensures that this manual trigger only appears when the pipeline is running on the default branch, preventing accidental attempts to deploy feature branches directly to production.
The impact of this configuration is the creation of a controlled gate. By associating the job with an environment name and URL, GitLab creates a deployment record. This allows the system to track exactly which commit is currently active on the production server. When a deployment is successfully triggered, GitLab calculates the commit-diffs between the latest deployment and the previous one, allowing the team to track exactly which merge requests were included in that specific release.
Advanced Control and Job Security
For sensitive environments, simply having a "Run" button is often insufficient. GitLab provides advanced mechanisms to prevent accidental triggers and unauthorized deployments.
Manual Confirmation
To mitigate the risk of accidental clicks—which could lead to catastrophic downtime in a production environment—GitLab offers the manual_confirmation keyword. When used in conjunction with when: manual, this setting forces the user to confirm the action through a secondary prompt before the job actually begins. This adds a layer of intentionality to the process, ensuring that the operator is fully aware of the action they are initiating.
Protected Environments
For users on Premium and Ultimate tiers, GitLab provides Protected Environments. This feature allows administrators to restrict who can trigger manual deployment jobs based on roles, specific users, or groups.
To secure a manual job using protected environments, the process is as follows:
- Define the environment in the job configuration within
.gitlab-ci.yml. - Navigate to the project settings for Protected Environments.
- Select the target environment (e.g., "production").
- Add the authorized users or groups to the "Allowed to Deploy" list.
This ensures that even if a user can see the "Run" button in the pipeline view, they cannot execute the job unless they are on the approved list. This is essential for compliance and security audits, as it prevents unauthorized personnel from pushing code to production.
Deployment Tracking and Lifecycle Management
A deployment in GitLab is defined as the act of pushing a version of code to an environment. Typically, there is only one active deployment per environment at any given time. GitLab provides a comprehensive history of these deployments, offering visibility into what is currently running on the servers.
Retry and Rollback Operations
When a deployment fails or introduces a critical bug, the ability to revert to a known stable state is vital. GitLab allows users to retry or roll back deployments through the "Operate > Environments" section of the left sidebar.
- Retrying a Deployment: If a deployment fails due to a transient network error or a runner issue, the user can select "Re-deploy to environment" to attempt the process again.
- Rolling Back: Users can select "Rollback environment" next to a previously successful deployment. This action points the environment back to the specific commit associated with that successful deployment.
It is important to note that for a rollback to succeed, the deployment process must be explicitly defined in the job's script. If the deployment relies on artifacts generated in previous jobs (such as Terraform plan files), and those artifacts have expired or need regeneration, the user must manually run the necessary precursor jobs from the pipelines page before the rollback can be executed.
Infrastructure Integration
GitLab integrates with external deployment services, such as Kubernetes, to assist in the deployment process. By linking a Kubernetes cluster to a project, GitLab can manage the deployment state more effectively and provide deeper insights into the health of the deployed application.
Strategic Use Cases for Manual Triggers
While the primary use case for manual jobs is production deployment, there are several strategic scenarios where manual triggers provide significant advantages.
Parameterized Testing and PoCs
In complex build processes, there is often a need to run a pipeline with custom parameters. For instance, when developing a Proof of Concept (PoC) on a separate branch, a developer may want to verify that the CI pipeline can handle the codebase without pushing the resulting images to the official organizational registry. By using manual jobs, a developer can trigger a build that pushes the Docker image to a personal organization or a testing registry, allowing them to verify the image and run it locally without breaking the main application flow.
Targeted Server Deployment
Manual jobs are highly effective when a developer needs to push changes to a specific subset of servers from a particular branch. Instead of automating a "push to all" strategy, the manual trigger allows the operator to choose the exact moment and target for the deployment, providing a level of granularity that fully automated pipelines cannot offer.
Troubleshooting and Performance Considerations
As the number of deployments increases, GitLab must manage the size of the Git repository to maintain performance. One common issue encountered by administrators is the "Deployment refs not found" error.
GitLab automatically deletes old deployment refs to keep the repository performant. In cases where a historical ref is needed for auditing or recovery on a GitLab Self-Managed instance, an administrator can restore these archived Git-refs by executing the following command in the Rails console:
ruby
Project.find_by_full_path(<your-project-full-path>).deployments.where(archived: true).each(&:create_ref)
It should be noted that GitLab may deprecate this support in the future due to the ongoing performance concerns associated with maintaining a massive number of Git-refs.
Comparison of Manual Job Types
The following table delineates the differences between optional and blocking manual jobs:
| Feature | Optional Manual Job | Blocking Manual Job |
|---|---|---|
| Definition Location | Outside of rules |
Inside rules |
allow_failure Value |
true (Default) |
false (Default) |
| Pipeline Impact | Does not affect overall status | Halts pipeline at current stage |
| Pipeline Result | Can succeed if job is skipped | Remains "running/blocked" until triggered |
| Primary Use Case | Non-critical tasks, optional cleanup | Production gates, mandatory approvals |
Pipeline Components and Execution Flow
To understand where manual deployments fit, it is necessary to examine the broader pipeline architecture. A GitLab CI/CD pipeline consists of three primary elements:
- Global YAML Keywords: These define the overall behavior of the pipeline, such as
workflow:rulesused to prevent duplicate pipelines. - Jobs: The atomic units of work that execute commands via runners (e.g., compiling, testing, deploying).
- Stages: Logical groupings of jobs. Stages run sequentially (e.g., Build $\rightarrow$ Test $\rightarrow$ Deploy). Jobs within a single stage run in parallel.
In a typical flow, a "Build" stage might contain a compile job. If successful, the pipeline moves to the "Test" stage, running test1 and test2 in parallel. Finally, the pipeline reaches the "Deploy" stage, where a manual job like deploy_prod waits for human intervention. If any job in a preceding stage fails, the pipeline usually ends early, and the manual deployment job is never reached, ensuring that broken code cannot be manually deployed to production.
Conclusion
The implementation of manual deployments in GitLab CI/CD is not a regression toward manual processes, but rather a sophisticated architectural choice that balances automation with governance. By utilizing when: manual, organizations can create a secure, auditable, and controlled release process. The ability to distinguish between optional and blocking jobs allows for flexibility in how pipelines are reported, while protected environments and manual confirmation settings provide the necessary safeguards for mission-critical infrastructure. When combined with GitLab's deployment tracking and rollback capabilities, manual triggers provide a safety net that ensures stability in the face of rapid iteration. The synergy between the .gitlab-ci.yml configuration, the GitLab UI's "Run" triggers, and the underlying Git-ref management creates a robust ecosystem capable of handling everything from simple PoCs to complex, multi-tier enterprise deployments.