GitLab Pipeline Waiting for Manual Approval

In a continuous delivery workflow, it is often necessary to require manual confirmation before proceeding with a production deployment. This post walks through how to implement that, including a pitfall I ran into along the way.

How to Implement It

The solution is actually quite simple, as shown below:

1
2
3
4
5
6
waiting-for-approval:
stage: waiting-for-approval
when: manual
allow_failure: false
script:
- echo "waiting-for-approval"

There are two key fields here: when and allow_failure.

when

This field specifies under what conditions the job should run. The accepted values are:

  • on_success (default): The job runs only after all jobs in the previous stage have succeeded, or all jobs in the previous stage are configured with allow_failure: true.
  • manual: The job is triggered manually.
  • always: The job always runs, regardless of whether the previous stage succeeded or failed.
  • on_failure: The job runs when at least one job in the previous stage has failed.
  • delayed: The job runs after a specified delay.
  • never: The job never runs.

allow_failure

This field determines whether the pipeline should continue when the current job fails. The value can be true or false.

Pay close attention to the default value — this is where the pitfall lies.

  • When the job has when: manual, the default value is true.
  • When the job has when: manual and also configures rules, the default value is false.
  • In all other cases, the default value is true.

The Pitfall

Hitting the Problem

I wanted to implement a pipeline that looks like this:

My .gitlab-ci.yml was as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
image: alpine
stages:
- test
- build
- deploy-to-qa
- waiting-for-approval
- deploy-to-prod

test:
stage: test
script:
- echo "test"

build:
stage: build
script:
- echo "build"

deploy-to-qa:
stage: deploy-to-qa
script:
- echo "deploy-to-qa"

waiting-for-approval:
stage: waiting-for-approval
when: manual
script:
- echo "waiting-for-approval"

deploy-to-prod:
stage: deploy-to-prod
script:
- echo "deploy-to-prod"

The actual execution result was:

I hadn’t clicked approval yet — so why did deploy-to-prod already run? And the pipeline status showed passed — how was it already passed?

What Went Wrong?

I went back to the documentation — Keyword reference for the .gitlab-ci.yml file | GitLab — and under Additional details found the following:

The default behavior of allow_failure changes to true with when: manual. However, if you use when: manual with [rules](https://docs.gitlab.com/ee/ci/yaml/#rules), allow_failure defaults to false.

Mystery solved!

I also found another relevant documentation page: Choose when to run jobs | GitLab, which covers how to create a job that must be triggered manually.

The Fix

Update .gitlab-ci.yml as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
image: alpine
stages:
- test
- build
- deploy-to-qa
- waiting-for-approval
- deploy-to-prod

test:
stage: test
script:
- echo "test"

build:
stage: build
script:
- echo "build"

deploy-to-qa:
stage: deploy-to-qa
script:
- echo "deploy-to-qa"

waiting-for-approval:
stage: waiting-for-approval
when: manual
allow_failure: false
script:
- echo "waiting-for-approval"

deploy-to-prod:
stage: deploy-to-prod
script:
- echo "deploy-to-prod"

The updated execution results: