when: manual and allow_failure in GitLab CI: Building Safe Deployment Gates
AI generated
CI/CD
.yml
GitLab · CI/CD · Deployment Gates
Combining when: manual and allow_failure
Controlled approvals instead of automatic deploys

when: manual and allow_failure solve two different problems in GitLab CI: one controls who is allowed to trigger a step, the other whether a failure blocks the pipeline. Combined correctly, they produce safe rollout gates for production deploys and optional extra steps that never hold anyone up.

16 min read when: manual allow_failure Environments Rollout gates

1. Why not every job should run automatically

A standard GitLab CI pipeline runs from stage to stage as soon as the previous job succeeds. For build and test steps that is exactly the desired behavior: every commit should be checked automatically without anyone having to press a button. But once steps have real, often irreversible consequences, such as deploying to a production server or deleting resources, a fully automatic flow becomes risky. A broken merge that happens to pass every test would otherwise go live unchecked.

GitLab CI offers two independent, but frequently combined, controls for this: when: manual pauses a job until someone explicitly starts it from the pipeline view, and allow_failure decouples a job's success from the pipeline's overall status. Both keywords solve different problems, but in practice they often complement each other into robust approval and rollout patterns.

2. when: manual basics: jobs that wait for a click

Setting when: manual removes a job from the automatic flow. It appears in the pipeline graph as a play-button icon and waits until a user with sufficient permissions starts it manually. Until then the pipeline stays pending on that branch of the graph, but does not necessarily block subsequent automatic jobs in other stages, unless those explicitly depend on the manual job via needs:.

In the example, deploy-production is a classic manual job: it only runs in the deploy stage, is tied to environment: production, and is not triggered automatically after tests succeed. Only a team member who considers the changes ready clicks Play in the GitLab UI. That creates a deliberate human checkpoint exactly where automated safety alone is not enough.


deploy-production:
  stage: deploy
  environment:
    name: production
    url: https://shop.example.com
  script:
    - ./deploy.sh production
  when: manual
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual

3. allow_failure basics: optional steps without blocking

allow_failure: true reverses the effect of a failed job: instead of marking the entire pipeline as failed, GitLab flags the job itself with a yellow warning icon while the pipeline overall still counts as successful and subsequent stages start normally. This is ideal for steps whose result matters but should not block, for example a security scan whose findings get triaged manually, or a linter whose warnings the team deliberately does not treat as a hard gate criterion.

In the example, the security-scan job is allowed to fail without turning the merge request status red. The team still sees the warning clearly in the pipeline widget and can review it deliberately, instead of every finding automatically blocking every merge. It is important to use allow_failure: true deliberately and not as a blanket fix for flaky tests, since that lets real failures slip through unnoticed.


security-scan:
  stage: test
  script:
    - composer audit --format=json
  allow_failure: true
  artifacts:
    reports:
      dependency_scanning: gl-dependency-scanning-report.json

4. Combining when: manual and allow_failure for optional manual steps

Combining both keywords on one job produces a step that neither starts automatically nor, if run and it fails, blocks the pipeline. This suits extra steps that should only run on demand, for example an expensive performance test before a major release that nobody wants triggered automatically on every ordinary merge, but whose failure also should not be a reason to block the merge.

In the example, performance-test waits to be started manually, and even a failure does not turn the pipeline red. That is deliberately different from deploy-production: there, a failure should indeed turn visibly red, because a failed deployment is a real problem. Whether allow_failure fits a manual job therefore depends on whether a failure of that specific step should really be a blocker.


performance-test:
  stage: test
  script:
    - k6 run --vus 50 --duration 2m loadtest.js
  when: manual
  allow_failure: true

5. Production deploy as a rollout gate with protected environments

For real production deploys, when: manual alone is often not enough, because GitLab by default lets any user with the Developer role trigger manual jobs. The complement is a protected environment: under Project Settings > CI/CD Settings > Protected Environments, it can be configured that only users with the Maintainer role or an explicitly named group may start the job for environment: production. That keeps when: manual technically simple while access control is enforced through GitLab's permissions instead of mere convention.

Adding needs: further enforces that a manual deploy job only becomes startable after certain prior jobs have succeeded, for example a smoke test on the staging environment. That creates a multi-stage gate: automated tests must be green, and only then does a human with sufficient permission decide on the final step.


smoke-test-staging:
  stage: verify
  environment:
    name: staging
  script:
    - ./smoke-test.sh staging

deploy-production:
  stage: deploy
  needs: ["smoke-test-staging"]
  environment:
    name: production
    url: https://shop.example.com
  script:
    - ./deploy.sh production
  when: manual

6. Manual rollback jobs that don't clutter the pipeline

A rollback job should exist in every pipeline but practically never get triggered. With when: manual it stays available, unused, in the background without disrupting the normal flow. If it is needed after all, for example because a deployment caused unexpected errors in production, a team member can start it directly from the same pipeline without creating a new pipeline or redeploying an old commit.

allow_failure: true also makes sense here, though for a different reason than with the performance test: if the rollback job is never triggered, it stays in the pipeline status as skipped or manual and does not negatively affect the overall status anyway. But if it is triggered and fails itself, for example because the previous deployment artifact is no longer available, that should not retroactively mark the whole pipeline as failed while the team is already manually working on the actual problem.


rollback-production:
  stage: deploy
  environment:
    name: production
    action: stop
  script:
    - ./deploy.sh production --rollback-to=$PREVIOUS_STABLE_TAG
  when: manual
  allow_failure: true

7. Difference between top-level when: manual and rules: with when: manual

When when: manual is set directly at the job level, it applies to every pipeline in which the job runs at all, regardless of branch or trigger. In many cases, though, a job should run automatically on main but stay manual on feature branches, or not appear at all for scheduled pipelines. That can only be expressed via rules: with conditional when: manual, since rules: allows a separate when: per condition.

In the example, deploy-production only gets the chance to be triggered manually on the main branch at all; on every other branch the job does not appear in the pipeline because no rules: condition matches. That is more precise than a blanket job-level when: manual, which would display the job on every branch, even where a production deploy never makes sense in the first place.


deploy-production:
  stage: deploy
  environment:
    name: production
  script:
    - ./deploy.sh production
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual
    - when: never

8. Setting timeout and expire_in sensibly for manual jobs

Manual jobs can in theory wait indefinitely for their trigger, which becomes a problem with artifacts: if a previous job's build output has long since been deleted via expire_in before anyone starts the manual deploy. Too short an expire_in causes a deploy job to fail because the required artifact no longer exists; too long an expire_in wastes storage in the project unnecessarily.

For production deploy artifacts, an expire_in of several days up to two weeks has proven reasonable, depending on how long can realistically pass between a successful build and the actual approval. A per-job timeout: can additionally be set in case the script itself might hang, for example a deployment waiting on an unreachable external resource.

9. Conclusion: design approval logic deliberately instead of combining at random

when: manual and allow_failure answer two separate questions: who is allowed to trigger a step, and does a failure block the pipeline. Applying both thoughtlessly to every critical job, without considering the consequence of each, leads either to needlessly blocked pipelines or to silently overlooked production failures.

The safe approach is to decide explicitly for every job: should it run automatically or manually, and should a failure turn visibly red or only appear as a warning. Combined with protected environments and needs: dependencies, this produces rollout gates that enforce real human control at the right points without losing the pipeline's overall degree of automation.

Combination Pipeline status on failure Start behavior Typical use
when: on_success (default) Pipeline fails Automatic after prior stage Build, unit tests
when: manual, no allow_failure Pipeline fails Waits for manual start Production deploy
when: manual + allow_failure: true Pipeline stays green Waits for manual start Optional tests, rollback
allow_failure: true, no manual Pipeline stays green Automatic after prior stage Security scan, linter

Mironsoft

CI/CD pipelines, zero-downtime deployments and release automation

Deployments that run without downtime and without the nail-biting?

We review existing GitLab pipelines for fragile deployment steps and missing safeguards, then build a release process with zero-downtime deployments, automated checks and a rollback you can actually trust in an emergency.

Pipeline Review

Checking an existing .gitlab-ci.yml for fragility, missing stages and security gaps.

Zero-Downtime Deployment

Building symlink releases, health checks and rollback strategies for Magento stores.

CI/CD Automation

Connecting tests, security scans and deployments into one reliable pipeline.

10. Summary

when: manual and allow_failure: The Essentials at a Glance

when: manual

Pauses a job until an authorized user explicitly starts it from the pipeline view.

allow_failure

Decouples a failed job from the pipeline's overall status, which still counts as successful.

Protected environments

Enforce that only authorized roles can trigger manual deploy jobs for protected environments.

rules: over job level

Conditional when: manual per branch or trigger is more precise than a blanket job-level setting.

11. FAQ: when: manual and allow_failure: The Essentials at a Glance

1What does when: manual do in GitLab CI?
The job is not started automatically after its prior stage but appears as a play button in the pipeline graph and waits until an authorized user triggers it manually.
2What is allow_failure: true for?
It ensures a failed job does not set the pipeline's overall status to failed. The job is marked as a warning while the pipeline still counts as successful.
3Can I use when: manual and allow_failure together?
Yes, this is a common combination for optional steps that only run manually on demand and whose failure should not block anyone, such as performance tests or rollback jobs.
4Who is allowed to trigger a manual job in GitLab CI?
By default, any user with the Developer role or higher in the project. For protected environments this can be restricted to Maintainers or specific groups via Protected Environments.
5What is a protected environment and what is it used for?
An environment marked as protected in CI/CD Settings, for which only authorized roles may trigger deploy jobs. It prevents any project member from triggering production deploys.
6Does job-level when: manual differ from rules: with when: manual?
Yes, at the job level when: manual applies to every pipeline the job runs in. Via rules:, when: manual can be tied to conditions like branch or pipeline source, and the job can be hidden entirely on other branches.
7Does a manual job automatically block subsequent stages?
Only if a subsequent job explicitly depends on the manual job via needs:. Without that dependency, other branches of the pipeline can continue independently.
8How long does a manual job stay triggerable?
As long as the pipeline has not expired and the related artifacts, controlled via artifacts: expire_in, still exist. After that the job can still be started but fails for lack of artifacts.
9Is allow_failure suitable for flaky tests?
Only as a short-term stopgap. Used permanently, it hides real regressions because failures no longer stand out. It is better to fix the root cause of the instability.
10How do I build a multi-stage approval gate with these keywords?
Automated tests and smoke tests run first without when: manual. The actual deploy job uses when: manual, needs: on the prior jobs, and a protected environment for access control.