doc/ci/jobs/job_rules.md
{{< details >}}
{{< /details >}}
Use the rules keyword to include or exclude jobs in pipelines.
Rules are evaluated in order until the first match. When a match is found, the job is either included or excluded from the pipeline, depending on the configuration.
You cannot use dotenv variables created in job scripts in rules, because rules are evaluated before any jobs run.
rules examplesThe following example uses if to define that the job runs in only two specific cases:
job:
script: echo "Hello, Rules!"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
allow_failure: true
- if: $CI_PIPELINE_SOURCE == "schedule"
when: manual (manual job)allow_failure: true (the pipeline continues running even if the manual job is not run)when: on_success (default)allow_failure: false (default)Alternatively, you can define a set of rules to exclude jobs in a few cases, but run them in all other cases:
job:
script: echo "Hello, Rules!"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: never
- if: $CI_PIPELINE_SOURCE == "schedule"
when: never
- when: on_success
when: on_success.[!warning] If you use a
whenclause as the final rule (not includingwhen: never), two simultaneous pipelines may start. Both push pipelines and merge request pipelines can be triggered by the same event (a push to the source branch for an open merge request). See how to avoid duplicate pipelines for more details.
You can configure a job to be executed only when the pipeline has been scheduled. For example:
job:on-schedule:
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
script:
- make world
job:
rules:
- if: $CI_PIPELINE_SOURCE == "push"
script:
- make build
In this example, make world runs in scheduled pipelines, and make build
runs in branch and tag pipelines.
Use rules:changes:compare_to to
skip a job when the branch is empty, which saves CI/CD resources. The configuration compares the
branch to the default branch, and if the branch:
For example, in a project with main as the default branch:
job:
script:
- echo "This job only runs for branches that are not empty"
rules:
- if: $CI_COMMIT_BRANCH
changes:
compare_to: 'refs/heads/main'
paths:
- '**/*'
The rule for this job compares all files and paths in the current branch
recursively (**/*) against the main branch. The rule matches and the
job runs only when there are changes to the files in the branch.
You can use rules: exists to configure a job to run only when a specific file does not exist.
For example, to run a job in a merge request pipeline when the example.yml file does not exist:
job:
script: echo "Hello, Rules!"
rules:
- exists:
- "example_dir/example.yml"
when: never
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
In this example, if the example_dir/example.yml file exists in the branch, the job does not run.
If the file does not exist, the job can run in merge request pipelines.
if clauses with predefined variablesrules:if clauses are commonly used with predefined CI/CD variables,
especially CI_PIPELINE_SOURCE.
The following example runs the job as a manual job in scheduled pipelines or in push
pipelines (to branches or tags), with when: on_success (default). It does not
add the job to any other pipeline type.
job:
script: echo "Hello, Rules!"
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
when: manual
allow_failure: true
- if: $CI_PIPELINE_SOURCE == "push"
The following example runs the job as a when: on_success job in merge request pipelines
and scheduled pipelines. It does not run in any other pipeline type.
job:
script: echo "Hello, Rules!"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_PIPELINE_SOURCE == "schedule"
Other commonly used if clauses:
if: $CI_COMMIT_TAG: If changes are pushed for a tag.if: $CI_COMMIT_BRANCH: If changes are pushed to any branch.if: $CI_COMMIT_BRANCH == "main": If changes are pushed to main.if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH: If changes are pushed to the default branch.if: $CI_COMMIT_BRANCH =~ /regex-expression/: If the commit branch matches a regular expression.if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_COMMIT_TITLE =~ /Merge branch.*/:
If the commit branch is the default branch and the commit message title matches a regular expression.if: $CUSTOM_VARIABLE == "value1": If the custom variable CUSTOM_VARIABLE is exactly value1.You can use predefined CI/CD variables with rules to choose which pipeline types jobs should run for.
The following table lists some of the variables that you can use, and the pipeline types the variables can control for:
push events to a branch, like new commits or tags.| Variables | Branch | Tag | Merge request | Scheduled |
|---|---|---|---|---|
CI_COMMIT_BRANCH | Yes | Yes | ||
CI_COMMIT_TAG | Yes | Yes, if the scheduled pipeline is configured to run on a tag. | ||
CI_PIPELINE_SOURCE = push | Yes | Yes | ||
CI_PIPELINE_SOURCE = schedule | Yes | |||
CI_PIPELINE_SOURCE = merge_request_event | Yes | |||
CI_MERGE_REQUEST_IID | Yes |
For example, to configure a job to run for merge request pipelines and scheduled pipelines, but not branch or tag pipelines:
job1:
script:
- echo
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_PIPELINE_SOURCE == "schedule"
- if: $CI_PIPELINE_SOURCE == "push"
when: never
CI_PIPELINE_SOURCE predefined variableUse the CI_PIPELINE_SOURCE variable to control when to add jobs for these pipeline types:
| Value | Description |
|---|---|
api | For pipelines triggered by the pipelines API. |
chat | For pipelines created by using a GitLab ChatOps command. |
external | When you use CI services other than GitLab. |
external_pull_request_event | When an external pull request on GitHub is created or updated. |
merge_request_event | For pipelines created when a merge request is created or updated. Required to enable merge request pipelines, merged results pipelines, and merge trains. |
ondemand_dast_scan | For DAST on-demand scan pipelines. |
ondemand_dast_validation | For DAST on-demand validation pipelines |
parent_pipeline | For pipelines triggered by a parent/child pipeline. Use this pipeline source in the child pipeline configuration so that it can be triggered by the parent pipeline. |
pipeline | For multi-project pipelines. |
push | For pipelines triggered by a Git push event, including for branches and tags. |
schedule | For scheduled pipelines. |
security_orchestration_policy | For scheduled scan execution policies pipelines. |
trigger | For pipelines created by using a trigger token. |
web | For pipelines created by selecting New pipeline in the GitLab UI, from the project's Build > Pipelines section. |
webide | For pipelines created by using the Web IDE. |
These values are the same as returned for the source parameter when using the
pipelines API endpoint.
You can use all rules keywords, like if, changes, and exists, in the same
rule. The rule evaluates to true only when all included keywords evaluate to true.
For example:
docker build:
script: docker build -t my-image:$CI_COMMIT_REF_SLUG .
rules:
- if: $VAR == "string value"
changes: # Include the job and set to when:manual if any of the follow paths match a modified file.
- Dockerfile
- docker/scripts/**/*
when: manual
allow_failure: true
If the Dockerfile file or any file in /docker/scripts has changed and $VAR == "string value",
then the job runs manually and is allowed to fail.
You can use parentheses with && and || to build more complicated variable expressions.
job1:
script:
- echo This rule uses parentheses.
rules:
- if: ($CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_BRANCH == "develop") && $MY_VARIABLE
If a job uses rules, a single action, like pushing a commit to a branch, can trigger
multiple pipelines. You don't have to explicitly configure rules for multiple types
of pipeline to trigger them accidentally.
For example:
job:
script: echo "This job creates double pipelines!"
rules:
- if: $CUSTOM_VARIABLE == "false"
when: never
- when: always
This job does not run when $CUSTOM_VARIABLE is false, but it does run in all
other pipelines, including both push (branch) and merge request pipelines. With
this configuration, every push to an open merge request's source branch
causes duplicated pipelines.
To avoid duplicate pipelines, you can:
Use workflow to specify which types of pipelines
can run.
Rewrite the rules to run the job only in very specific cases,
and avoid a final when rule:
job:
script: echo "This job does NOT create double pipelines!"
rules:
- if: $CUSTOM_VARIABLE == "true" && $CI_PIPELINE_SOURCE == "merge_request_event"
You can also avoid duplicate pipelines by changing the job rules to avoid either push (branch)
pipelines or merge request pipelines. However, if you use a - when: always rule without
workflow: rules, GitLab displays a pipeline warning.
For example, the following does not trigger double pipelines, but is not recommended
without workflow: rules:
job:
script: echo "This job does NOT create double pipelines!"
rules:
- if: $CI_PIPELINE_SOURCE == "push"
when: never
- when: always
You should not include both push and merge request pipelines in the same job without
workflow:rules that prevent duplicate pipelines:
job:
script: echo "This job creates double pipelines!"
rules:
- if: $CI_PIPELINE_SOURCE == "push"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
Also, do not mix only/except jobs with rules jobs in the same pipeline.
It may not cause YAML errors, but the different default behaviors of only/except
and rules can cause issues that are difficult to troubleshoot:
job-with-no-rules:
script: echo "This job runs in branch pipelines."
job-with-rules:
script: echo "This job runs in merge request pipelines."
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
For every change pushed to the branch with an open merge request, duplicate pipelines run.
One branch pipeline runs a single job (job-with-no-rules), and one merge request pipeline
runs the other job (job-with-rules). Jobs with no rules default
to except: merge_requests, so job-with-no-rules
runs in all cases except merge requests.
Use !reference tags to reuse rules in different
jobs. You can combine !reference rules with rules defined in the job. For example:
.default_rules:
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
when: never
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
job1:
rules:
- !reference [.default_rules, rules]
script:
- echo "This job runs for the default branch, but not schedules."
job2:
rules:
- !reference [.default_rules, rules]
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- echo "This job runs for the default branch, but not schedules."
- echo "It also runs for merge requests."
Use variable expressions with rules:if to control
when jobs should be added to a pipeline.
You can use the equality operators == and != to compare a variable with a
string. Both single quotes and double quotes are valid. The variable has to be on the left side of the comparison. For example:
if: $VARIABLE == "some value"if: $VARIABLE != "some value"You can compare the values of two variables. For example:
if: $VARIABLE_1 == $VARIABLE_2if: $VARIABLE_1 != $VARIABLE_2You can compare a variable to the null keyword to see if it is defined. For example:
if: $VARIABLE == nullif: $VARIABLE != nullYou can check if a variable is defined but empty. For example:
if: $VARIABLE == ""if: $VARIABLE != ""You can check if a variable is both defined and not empty by using just the variable name in the expression. For example:
if: $VARIABLEYou can also use CI/CD inputs in variable expressions.
You can do regular expression matching on variable values with the =~ and !~ operators.
Expressions evaluate as true if:
=~.!~.For example:
if: $VARIABLE =~ /^content.*/if: $VARIABLE !~ /^content.*/Additionally:
/./, are not supported and
produce an invalid expression syntax error.i flag modifier to make a
pattern case-insensitive. For example: /pattern/i./. For example, you can't use issue-/.*/
to match all tag names or branch names that begin with issue-, but you can use /issue-.*/.@ symbol denotes the beginning of a ref's repository path.
To match a ref name that contains the @ character in a regular expression,
you must use the hex character code match \x40.^ and $ to avoid the regular expression matching only a substring
of the tag name or branch name. For example, /^issue-.*$/ is equivalent to /^issue-/,
while just /issue/ would also match a branch called severe-issues.Variables on the right side of =~ and !~ expressions are evaluated as regular expressions.
The regular expression must be enclosed in forward slashes (/). For example:
variables:
pattern: '/^ab.*/'
regex-job1:
variables:
teststring: 'abcde'
script: echo "This job will run, because 'abcde' matches the /^ab.*/ pattern."
rules:
- if: '$teststring =~ $pattern'
regex-job2:
variables:
teststring: 'fghij'
script: echo "This job will not run, because 'fghi' does not match the /^ab.*/ pattern."
rules:
- if: '$teststring =~ $pattern'
Variables in a regular expression are not expanded. For example:
variables:
string1: 'regex-job1'
string2: 'regex-job2'
pattern: '/$string2/'
regex-job1:
script: echo "This job will NOT run, because the 'string1' variable inside the regex pattern is not expanded."
rules:
- if: '$CI_JOB_NAME =~ /$string1/'
regex-job2:
script: echo "This job will NOT run, because the 'string2' variable inside the 'pattern' variable is not expanded."
rules:
- if: '$CI_JOB_NAME =~ $pattern'
You can join multiple expressions using && (and) or || (or), for example:
$VARIABLE1 =~ /^content.*/ && $VARIABLE2 == "something"$VARIABLE1 =~ /^content.*/ && $VARIABLE2 =~ /thing$/ && $VARIABLE3$VARIABLE1 =~ /^content.*/ || $VARIABLE2 =~ /thing$/ && $VARIABLE3You can use parentheses to group expressions together. Parentheses take precedence over
&& and ||, so expressions enclosed in parentheses evaluate first, and the
result is used for the rest of the expression. For the precedence of operators,
&& evaluates before ||.
Nest parentheses to create complex conditions, and the inner-most expressions in parentheses evaluate first. For example:
($VARIABLE1 =~ /^content.*/ || $VARIABLE2) && ($VARIABLE3 =~ /thing$/ || $VARIABLE4)($VARIABLE1 =~ /^content.*/ || $VARIABLE2 =~ /thing$/) && $VARIABLE3$CI_COMMIT_BRANCH == "my-branch" || (($VARIABLE1 == "thing" || $VARIABLE2 == "thing") && $VARIABLE3){{< history >}}
{{< /history >}}
You can use the ! operator to negate an expression, or a part of an expression.
For example:
if: "!$VAR1": True when the variable is empty or undefined.if: !($VAR1 == "my variable"): True when the variable value does not match my variable.if: $VAR1 && !$VAR2: True when VAR1 exists and isn't empty, and VAR2 doesn't exist or is empty.if: !($VAR1 || $VAR2): True only when both variables don't exist or are empty.if: !($VAR1 && $VAR2): True when either variable doesn't exist or is empty.[!warning] The
!operator checks if a variable is empty or undefined, not whether its value isfalseor0. For example:
!"false"evaluates tofalsebecause the string"false"is not empty (non-empty strings are truthy).!"0"also evaluates tofalsebecause the string is not empty.!""evaluates totruebecause the string is empty (empty strings are falsy).To check specific values, use comparison operators, for example
!($VAR == "false")or!($VAR == "0").
only or except to rulesUse rules and CI/CD variable expressions to reproduce the same behavior as the deprecated
only and except keywords.
For example, starting with this deprecated configuration:
job1:
script: echo
only:
- main
- /^stable-branch.*$/
- schedules
job2:
script: echo
except:
- main
- /^issue-.*$/
- merge_requests
In this example:
job1 uses only to run in pipelines when:
main)./^stable-branch.*$/.job2 uses except to skip pipelines when:
main)./^issue-.*$/.To create similar pipeline configuration with rules, use CI/CD variable expressions.
For example, for a direct migration from only and except to rules:
job1:
script: echo
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_BRANCH =~ /^stable-branch.*$/
- if: $CI_PIPELINE_SOURCE == "schedule"
job2:
script: echo
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: never
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: never
- if: $CI_COMMIT_BRANCH =~ /^issue-.*$/
when: never
- when: on_success
Both jobs behave the same way with rules as with only and except.
However, you can simplify job2 to avoid when: never rules.
Define rules for when job2 should run instead of when it should not run.
For example, if job2 should run for all branches except the default branch, and also for tags:
job2:
script: echo
rules:
- if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_TAG
In this example, job2 runs when the branch is not the default branch,
and when a new Git tag is created. Otherwise, the job does not run.
=~When using the =~ character, make sure the right side of the comparison always contains
a valid regular expression.
If the right side of the comparison is not a valid regular expression enclosed with / characters,
the expression evaluates in an unexpected way. In that case, the comparison checks
if the left side is a substring of the right side. For example, "23" =~ "1234" evaluates to true,
which is the opposite of "23" =~ /1234/, which evaluates to false.
You should not configure your pipeline to rely on this behavior.