ℹ️ Skipped - page is already crawled
| Filter | Status | Condition | Details |
|---|---|---|---|
| HTTP status | PASS | download_http_code = 200 | HTTP 200 |
| Age cutoff | PASS | download_stamp > now() - 6 MONTH | 0.7 months ago |
| History drop | PASS | isNull(history_drop_reason) | No drop reason |
| Spam/ban | PASS | fh_dont_index != 1 AND ml_spam_score = 0 | ml_spam_score=0 |
| Canonical | PASS | meta_canonical IS NULL OR = '' OR = src_unparsed | Not set |
| Property | Value |
|---|---|
| URL | https://docs.gitlab.com/ci/jobs/job_rules/ |
| Last Crawled | 2026-03-17 19:17:14 (20 days ago) |
| First Indexed | 2025-02-21 02:20:29 (1 year ago) |
| HTTP Status Code | 200 |
| Meta Title | Specify when jobs run with rules | GitLab Docs |
| Meta Description | GitLab product documentation. |
| Meta Canonical | null |
| Boilerpipe Text | Tier
: Free, Premium, Ultimate
Offering
: GitLab.com, GitLab Self-Managed, GitLab Dedicated
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
examples
The following example uses
if
to define that the job runs in only two specific cases:
yaml
job
:
script
:
echo "Hello, Rules!"
rules
:
-
if
:
$CI_PIPELINE_SOURCE == "merge_request_event"
when
:
manual
allow_failure
:
true
-
if
:
$CI_PIPELINE_SOURCE == "schedule"
If the pipeline is for a merge request, the first rule matches, and the job
is added to the merge request pipeline with attributes of:
when: manual
(manual job)
allow_failure: true
(the pipeline continues running even if the manual job is not run)
If the pipeline is not for a merge request, the first rule doesn’t match, and the
second rule is evaluated.
If the pipeline is a scheduled pipeline, the second rule matches, and the job
is added to the scheduled pipeline. No attributes were defined, so it is added
with:
when: on_success
(default)
allow_failure: false
(default)
In all other cases, no rules match, so the job is not added to any other pipeline.
Alternatively, you can define a set of rules to exclude jobs in a few cases, but
run them in all other cases:
yaml
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
If the pipeline is for a merge request, the job is not added to the pipeline.
If the pipeline is a scheduled pipeline, the job is not added to the pipeline.
In all other cases, the job is added to the pipeline, with
when: on_success
.
If you use a
when
clause as the final rule (not including
when: 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.
Run jobs for scheduled pipelines
You can configure a job to be executed only when the pipeline has been
scheduled. For example:
yaml
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.
Skip jobs if the branch is empty
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:
Doesn’t have changed files, the job doesn’t run.
Has changed files, the job runs.
For example, in a project with
main
as the default branch:
yaml
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.
Run a job when a file is not present
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:
yaml
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.
Common
if
clauses with predefined variables
rules: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.
yaml
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.
yaml
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
.
Run jobs only in specific pipeline types
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:
Branch pipelines that run for Git
push
events to a branch, like new commits or tags.
Tag pipelines that run only when a new Git tag is pushed to a branch.
Merge request pipelines that run for changes to a merge request, like new commits or
selecting
Run pipeline
in a merge request’s pipelines tab.
Scheduled pipelines.
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:
yaml
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 variable
Use 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
.
Complex rules
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:
yaml
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.
yaml
job1
:
script
:
-
echo This rule uses parentheses.
rules
:
-
if
:
($CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_BRANCH == "develop") && $MY_VARIABLE
Avoid duplicate pipelines
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:
yaml
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:
yaml
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
:
yaml
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
:
yaml
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:
yaml
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.
Reuse rules in different jobs
Use
!reference
tags
to reuse rules in different
jobs. You can combine
!reference
rules with rules defined in the job. For example:
yaml
.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."
CI/CD variable expressions
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_2
if: $VARIABLE_1 != $VARIABLE_2
You can compare a variable to the
null
keyword to see if it is defined. For example:
if: $VARIABLE == null
if: $VARIABLE != null
You 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: $VARIABLE
You can also
use CI/CD inputs in variable expressions
.
Compare a variable to a regular expression
You can do regular expression matching on variable values with the
=~
and
!~
operators.
Expressions evaluate as
true
if:
Matches are found when using
=~
.
Matches are not found when using
!~
.
For example:
if: $VARIABLE =~ /^content.*/
if: $VARIABLE !~ /^content.*/
Additionally:
Single-character regular expressions, like
/./
, are not supported and
produce an
invalid expression syntax
error.
Pattern matching is case-sensitive by default. Use the
i
flag modifier to make a
pattern case-insensitive. For example:
/pattern/i
.
Only the tag or branch name can be matched by a regular expression.
The repository path, if given, is always matched literally.
The entire pattern must be surrounded by
/
. For example, you can’t use
issue-/.*/
to match all tag names or branch names that begin with
issue-
, but you can use
/issue-.*/
.
The
@
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
.
Use anchors
^
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
.
Variable pattern matching with regular expressions uses the
RE2 regular expression syntax
.
Store a regular expression in a variable
Variables on the right side of
=~
and
!~
expressions are evaluated as regular expressions.
The regular expression must be enclosed in forward slashes (
/
). For example:
yaml
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:
yaml
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'
Join variable expressions together
You can join multiple expressions using
&&
(and) or
||
(or), for example:
$VARIABLE1 =~ /^content.*/ && $VARIABLE2 == "something"
$VARIABLE1 =~ /^content.*/ && $VARIABLE2 =~ /thing$/ && $VARIABLE3
$VARIABLE1 =~ /^content.*/ || $VARIABLE2 =~ /thing$/ && $VARIABLE3
You 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)
Migrate from
only
or
except
to
rules
Use
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:
yaml
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:
The branch is the default branch (
main
).
The branch name matches the pattern
/^stable-branch.*$/
.
The pipeline runs on a schedule.
job2
uses
except
to skip pipelines when:
The branch is the default branch (
main
).
The branch name matches the pattern
/^issue-.*$/
.
The pipeline is a merge request pipeline.
To create similar pipeline configuration with
rules
, use CI/CD variable expressions.
For example, for a direct migration from
only
and
except
to
rules
:
yaml
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:
yaml
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.
Troubleshooting
Unexpected behavior from regular expression matching with
=~
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. |
| Markdown | [Skip to main content](https://docs.gitlab.com/ci/jobs/job_rules/#skipTarget) [Go to GitLab Docs homepage](https://docs.gitlab.com/)
[What's new?](https://about.gitlab.com/releases/whats-new/)
English
- Language
- English
- 日本語
v18.10
- - [18\.10 (not yet released)](https://docs.gitlab.com/ci/jobs/job_rules/)
- - [18\.9 (recently released)](https://docs.gitlab.com/18.9/ci/jobs/job_rules/)
- [18\.8](https://docs.gitlab.com/18.8/ci/jobs/job_rules/)
- [18\.7](https://docs.gitlab.com/18.7/ci/jobs/job_rules/)
- - [17\.11](https://docs.gitlab.com/17.11/ci/jobs/job_rules/)
- [16\.11](https://docs.gitlab.com/16.11/ee/ci/jobs/job_rules.html)
- - [Archives](https://docs.gitlab.com/archives)
Select theme and layout
- Light mode
- Dark mode
- Auto
- Fixed width
- Fluid width
[What's new?](https://about.gitlab.com/releases/whats-new/) [Get free trial](https://gitlab.com/-/trial_registrations/new?glm_source=docs.gitlab.com&glm_content=navigation-cta-docs)
Toggle menu
- [Use GitLab](https://docs.gitlab.com/user/)
- [GitLab Duo](https://docs.gitlab.com/user/gitlab_duo/)
- [Extend](https://docs.gitlab.com/api/)
- [Install](https://docs.gitlab.com/install/)
- [Administer](https://docs.gitlab.com/administration/)
- [Subscribe](https://docs.gitlab.com/subscriptions/)
- [Contribute](https://docs.gitlab.com/development/)
- [Solutions](https://docs.gitlab.com/solutions/)
Select a topic
[Getting started](https://docs.gitlab.com/user/get_started/)
[Tutorials](https://docs.gitlab.com/tutorials/)
[Manage your organization](https://docs.gitlab.com/topics/set_up_organization/)
[Organize work with projects](https://docs.gitlab.com/user/project/organize_work_with_projects/)
[Plan and track work](https://docs.gitlab.com/topics/plan_and_track/)
[Manage authentication and authorization](https://docs.gitlab.com/auth/)
[Use Git](https://docs.gitlab.com/topics/git/)
[Manage your code](https://docs.gitlab.com/topics/manage_code/)
[Use CI/CD to build your application](https://docs.gitlab.com/topics/build_your_application/)
[Getting started](https://docs.gitlab.com/ci/)
[Tutorials](https://docs.gitlab.com/tutorials/build_application/)
[CI/CD YAML syntax reference](https://docs.gitlab.com/ci/yaml/)
[Runners](https://docs.gitlab.com/ci/runners/)
[Pipelines](https://docs.gitlab.com/ci/pipelines/)
[Jobs](https://docs.gitlab.com/ci/jobs/)
[Control how jobs run](https://docs.gitlab.com/ci/jobs/job_control/)
[Specify when jobs run with rules](https://docs.gitlab.com/ci/jobs/job_rules/)
[Troubleshooting](https://docs.gitlab.com/ci/jobs/job_troubleshooting/)
[Job inputs](https://docs.gitlab.com/ci/jobs/job_inputs/)
[Format scripts and job logs](https://docs.gitlab.com/ci/yaml/script/)
[Job execution flow](https://docs.gitlab.com/ci/jobs/job_execution/)
[Caching](https://docs.gitlab.com/ci/caching/)
[Artifacts](https://docs.gitlab.com/ci/jobs/job_artifacts/)
[SSH keys](https://docs.gitlab.com/ci/jobs/ssh_keys/)
[Docker](https://docs.gitlab.com/ci/docker/)
[Services](https://docs.gitlab.com/ci/services/)
[Git submodules](https://docs.gitlab.com/ci/runners/git_submodules/)
[Access a terminal for a running job](https://docs.gitlab.com/ci/interactive_web_terminal/)
[CI/CD job logs](https://docs.gitlab.com/ci/jobs/job_logs/)
[CI/CD components](https://docs.gitlab.com/ci/components/)
[Variables](https://docs.gitlab.com/ci/variables/)
[Pipeline security](https://docs.gitlab.com/ci/pipeline_security/)
[External secrets](https://docs.gitlab.com/ci/secrets/)
[Debugging](https://docs.gitlab.com/ci/debugging/)
[Auto DevOps](https://docs.gitlab.com/topics/autodevops/)
[Testing](https://docs.gitlab.com/ci/testing/)
[Google Cloud integration](https://docs.gitlab.com/ci/gitlab_google_cloud_integration/)
[Migrate to GitLab CI/CD](https://docs.gitlab.com/ci/migration/plan_a_migration/)
[External repository integrations](https://docs.gitlab.com/ci/ci_cd_for_external_repos/)
[Mobile DevOps](https://docs.gitlab.com/ci/mobile_devops/)
[Secure your application](https://docs.gitlab.com/user/application_security/secure_your_application/)
[Deploy and release your application](https://docs.gitlab.com/topics/release_your_application/)
[Manage your infrastructure](https://docs.gitlab.com/user/infrastructure/)
[Monitor your application](https://docs.gitlab.com/operations/)
[Analyze GitLab usage](https://docs.gitlab.com/user/analytics/)
[Feature support](https://docs.gitlab.com/policy/development_stages_support/)
[Find your GitLab version](https://docs.gitlab.com/user/version/)
/
1. Show more breadcrumbs
- [GitLab Docs](https://docs.gitlab.com/)
- [Use GitLab](https://docs.gitlab.com/user/)
2. [Use CI/CD to build your …](https://docs.gitlab.com/topics/build_your_application/)
3. [Jobs](https://docs.gitlab.com/ci/jobs/)
4. [Control how jobs run](https://docs.gitlab.com/ci/jobs/job_control/)
5. [Specify when jobs run with rules](https://docs.gitlab.com/ci/jobs/job_rules/)
***
# Specify when jobs run with `rules`
- Tier: Free, Premium, Ultimate
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
Use the [`rules`](https://docs.gitlab.com/ci/yaml/#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` examples
The following example uses `if` to define that the job runs in only two specific cases:
yaml
```
job:
script: echo "Hello, Rules!"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
allow_failure: true
- if: $CI_PIPELINE_SOURCE == "schedule"
```
- If the pipeline is for a merge request, the first rule matches, and the job is added to the merge request pipeline with attributes of:
- `when: manual` (manual job)
- `allow_failure: true` (the pipeline continues running even if the manual job is not run)
- If the pipeline is not for a merge request, the first rule doesn’t match, and the second rule is evaluated.
- If the pipeline is a scheduled pipeline, the second rule matches, and the job is added to the scheduled pipeline. No attributes were defined, so it is added with:
- `when: on_success` (default)
- `allow_failure: false` (default)
- In all other cases, no rules match, so the job is not added to any other pipeline.
Alternatively, you can define a set of rules to exclude jobs in a few cases, but run them in all other cases:
yaml
```
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
```
- If the pipeline is for a merge request, the job is not added to the pipeline.
- If the pipeline is a scheduled pipeline, the job is not added to the pipeline.
- In all other cases, the job is added to the pipeline, with `when: on_success`.
If you use a `when` clause as the final rule (not including `when: 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](https://docs.gitlab.com/ci/jobs/job_rules/#avoid-duplicate-pipelines) for more details.
### Run jobs for scheduled pipelines
You can configure a job to be executed only when the pipeline has been scheduled. For example:
yaml
```
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.
### Skip jobs if the branch is empty
Use [`rules:changes:compare_to`](https://docs.gitlab.com/ci/yaml/#ruleschangescompare_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:
- Doesn’t have changed files, the job doesn’t run.
- Has changed files, the job runs.
For example, in a project with `main` as the default branch:
yaml
```
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.
## Run a job when a file is not present
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:
yaml
```
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.
## Common `if` clauses with predefined variables
`rules:if` clauses are commonly used with [predefined CI/CD variables](https://docs.gitlab.com/ci/variables/predefined_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.
yaml
```
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.
yaml
```
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`.
### Run jobs only in specific pipeline types
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:
- Branch pipelines that run for Git `push` events to a branch, like new commits or tags.
- Tag pipelines that run only when a new Git tag is pushed to a branch.
- Merge request pipelines that run for changes to a merge request, like new commits or selecting **Run pipeline** in a merge request’s pipelines tab.
- Scheduled pipelines.
| 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:
yaml
```
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 variable
Use 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](https://docs.gitlab.com/api/pipelines/#create-a-new-pipeline). |
| `chat` | For pipelines created by using a [GitLab ChatOps](https://docs.gitlab.com/ci/chatops/) command. |
| `external` | When you use CI services other than GitLab. |
| `external_pull_request_event` | When an [external pull request on GitHub](https://docs.gitlab.com/ci/ci_cd_for_external_repos/#pipelines-for-external-pull-requests) is created or updated. |
| `merge_request_event` | For pipelines created when a merge request is created or updated. Required to enable [merge request pipelines](https://docs.gitlab.com/ci/pipelines/merge_request_pipelines/), [merged results pipelines](https://docs.gitlab.com/ci/pipelines/merged_results_pipelines/), and [merge trains](https://docs.gitlab.com/ci/pipelines/merge_trains/). |
| `ondemand_dast_scan` | For [DAST on-demand scan](https://docs.gitlab.com/user/application_security/dast/on-demand_scan/) pipelines. |
| `ondemand_dast_validation` | For [DAST on-demand validation](https://docs.gitlab.com/user/application_security/dast/profiles/#site-profile-validation) pipelines |
| `parent_pipeline` | For pipelines triggered by a [parent/child pipeline](https://docs.gitlab.com/ci/pipelines/downstream_pipelines/#parent-child-pipelines). Use this pipeline source in the child pipeline configuration so that it can be triggered by the parent pipeline. |
| `pipeline` | For [multi-project pipelines](https://docs.gitlab.com/ci/pipelines/downstream_pipelines/#multi-project-pipelines). |
| `push` | For pipelines triggered by a Git push event, including for branches and tags. |
| `schedule` | For [scheduled pipelines](https://docs.gitlab.com/ci/pipelines/schedules/). |
| `security_orchestration_policy` | For [scheduled scan execution policies](https://docs.gitlab.com/user/application_security/policies/scan_execution_policies/) pipelines. |
| `trigger` | For pipelines created by using a [trigger token](https://docs.gitlab.com/ci/triggers/#configure-cicd-jobs-to-run-in-triggered-pipelines). |
| `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](https://docs.gitlab.com/user/project/web_ide/). |
These values are the same as returned for the `source` parameter when using the [pipelines API endpoint](https://docs.gitlab.com/api/pipelines/#list-project-pipelines).
## Complex rules
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:
yaml
```
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.
yaml
```
job1:
script:
- echo This rule uses parentheses.
rules:
- if: ($CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_BRANCH == "develop") && $MY_VARIABLE
```
## Avoid duplicate pipelines
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:
yaml
```
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`](https://docs.gitlab.com/ci/yaml/#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:
yaml
```
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](https://docs.gitlab.com/ci/debugging/#pipeline-warnings).
For example, the following does not trigger double pipelines, but is not recommended without `workflow: rules`:
yaml
```
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](https://docs.gitlab.com/ci/yaml/workflow/#switch-between-branch-pipelines-and-merge-request-pipelines):
yaml
```
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:
yaml
```
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`](https://docs.gitlab.com/ci/yaml/deprecated_keywords/#only--except), so `job-with-no-rules` runs in all cases except merge requests.
## Reuse rules in different jobs
Use [`!reference` tags](https://docs.gitlab.com/ci/yaml/yaml_optimization/#reference-tags) to reuse rules in different jobs. You can combine `!reference` rules with rules defined in the job. For example:
yaml
```
.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."
```
## CI/CD variable expressions
Use variable expressions with [`rules:if`](https://docs.gitlab.com/ci/yaml/#rulesif) 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_2`
- `if: $VARIABLE_1 != $VARIABLE_2`
You can compare a variable to the `null` keyword to see if it is defined. For example:
- `if: $VARIABLE == null`
- `if: $VARIABLE != null`
You 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: $VARIABLE`
You can also [use CI/CD inputs in variable expressions](https://docs.gitlab.com/ci/inputs/examples/#use-cicd-inputs-in-variable-expressions).
### Compare a variable to a regular expression
You can do regular expression matching on variable values with the `=~` and `!~` operators.
Expressions evaluate as `true` if:
- Matches are found when using `=~`.
- Matches are not found when using `!~`.
For example:
- `if: $VARIABLE =~ /^content.*/`
- `if: $VARIABLE !~ /^content.*/`
Additionally:
- Single-character regular expressions, like `/./`, are not supported and produce an `invalid expression syntax` error.
- Pattern matching is case-sensitive by default. Use the `i` flag modifier to make a pattern case-insensitive. For example: `/pattern/i`.
- Only the tag or branch name can be matched by a regular expression. The repository path, if given, is always matched literally.
- The entire pattern must be surrounded by `/`. For example, you can’t use `issue-/.*/` to match all tag names or branch names that begin with `issue-`, but you can use `/issue-.*/`.
- The `@` 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`.
- Use anchors `^` 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`.
- Variable pattern matching with regular expressions uses the [RE2 regular expression syntax](https://github.com/google/re2/wiki/Syntax).
### Store a regular expression in a variable
Variables on the right side of `=~` and `!~` expressions are evaluated as regular expressions. The regular expression must be enclosed in forward slashes (`/`). For example:
yaml
```
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:
yaml
```
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'
```
### Join variable expressions together
You can join multiple expressions using `&&` (and) or `||` (or), for example:
- `$VARIABLE1 =~ /^content.*/ && $VARIABLE2 == "something"`
- `$VARIABLE1 =~ /^content.*/ && $VARIABLE2 =~ /thing$/ && $VARIABLE3`
- `$VARIABLE1 =~ /^content.*/ || $VARIABLE2 =~ /thing$/ && $VARIABLE3`
You 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)`
## Migrate from `only` or `except` to `rules`
Use `rules` and CI/CD variable expressions to reproduce the same behavior as the deprecated [`only` and `except` keywords](https://docs.gitlab.com/ci/yaml/deprecated_keywords/#only--except).
For example, starting with this deprecated configuration:
yaml
```
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:
- The branch is the default branch (`main`).
- The branch name matches the pattern `/^stable-branch.*$/`.
- The pipeline runs on a schedule.
- `job2` uses `except` to skip pipelines when:
- The branch is the default branch (`main`).
- The branch name matches the pattern `/^issue-.*$/`.
- The pipeline is a merge request pipeline.
To create similar pipeline configuration with `rules`, use CI/CD variable expressions. For example, for a direct migration from `only` and `except` to `rules`:
yaml
```
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:
yaml
```
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.
## Troubleshooting
### Unexpected behavior from regular expression matching with `=~`
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.
Was this page helpful?
Yes
No
Edit this page
- - [Open in Web IDE`.`Quickly and easily edit multiple files.](https://gitlab.com/-/ide/project/gitlab-org/gitlab/edit/master/-/doc/ci/jobs/job_rules.md)
- [View page sourceEdit this file only.](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/ci/jobs/job_rules.md)
- [Create an issueSuggest improvements.](https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_template=Documentation)
- [rules examples](https://docs.gitlab.com/ci/jobs/job_rules/#rules-examples)
- [Run jobs for scheduled pipelines](https://docs.gitlab.com/ci/jobs/job_rules/#run-jobs-for-scheduled-pipelines)
- [Skip jobs if the branch is empty](https://docs.gitlab.com/ci/jobs/job_rules/#skip-jobs-if-the-branch-is-empty)
- [Run a job when a file is not present](https://docs.gitlab.com/ci/jobs/job_rules/#run-a-job-when-a-file-is-not-present)
- [Common if clauses with predefined variables](https://docs.gitlab.com/ci/jobs/job_rules/#common-if-clauses-with-predefined-variables)
- [Run jobs only in specific pipeline types](https://docs.gitlab.com/ci/jobs/job_rules/#run-jobs-only-in-specific-pipeline-types)
- [CI\_PIPELINE\_SOURCE predefined variable](https://docs.gitlab.com/ci/jobs/job_rules/#ci_pipeline_source-predefined-variable)
- [Complex rules](https://docs.gitlab.com/ci/jobs/job_rules/#complex-rules)
- [Avoid duplicate pipelines](https://docs.gitlab.com/ci/jobs/job_rules/#avoid-duplicate-pipelines)
- [Reuse rules in different jobs](https://docs.gitlab.com/ci/jobs/job_rules/#reuse-rules-in-different-jobs)
- [CI/CD variable expressions](https://docs.gitlab.com/ci/jobs/job_rules/#cicd-variable-expressions)
- [Compare a variable to a regular expression](https://docs.gitlab.com/ci/jobs/job_rules/#compare-a-variable-to-a-regular-expression)
- [Store a regular expression in a variable](https://docs.gitlab.com/ci/jobs/job_rules/#store-a-regular-expression-in-a-variable)
- [Join variable expressions together](https://docs.gitlab.com/ci/jobs/job_rules/#join-variable-expressions-together)
- [Migrate from only or except to rules](https://docs.gitlab.com/ci/jobs/job_rules/#migrate-from-only-or-except-to-rules)
- [Troubleshooting](https://docs.gitlab.com/ci/jobs/job_rules/#troubleshooting)
- [Unexpected behavior from regular expression matching with =~](https://docs.gitlab.com/ci/jobs/job_rules/#unexpected-behavior-from-regular-expression-matching-with-)
[](https://docs.gitlab.com/)
- [Facebook](https://www.facebook.com/gitlab)
- [LinkedIn](https://www.linkedin.com/company/gitlab-com)
- [Twitter](https://twitter.com/gitlab)
- [YouTube](https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg)
[](https://creativecommons.org/licenses/by-sa/4.0/)
Company
- [About GitLab](https://about.gitlab.com/company/)
- [View pricing](https://about.gitlab.com/pricing/)
- [Try GitLab for free](https://about.gitlab.com/free-trial/)
Feedback
- [View page source](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/ci/jobs/job_rules.md)
- [Edit in Web IDE](https://gitlab.com/-/ide/project/gitlab-org/gitlab/edit/master/-/doc/ci/jobs/job_rules.md)
- [Contribute to GitLab](https://about.gitlab.com/community/contribute/)
- [Suggest updates](https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_template=Documentation)
Help & Community
- [Get certified](https://university.gitlab.com/pages/certifications)
- [Get support](https://about.gitlab.com/support/)
- [Post on the GitLab forum](https://forum.gitlab.com/new-topic?title=topic%20title&body=topic%20body&tags=docs-feedback)
Resources
- [Terms](https://about.gitlab.com/terms/)
- [Privacy statement](https://about.gitlab.com/privacy/)
- [Use of generative AI](https://docs.gitlab.com/legal/use_generative_ai/)
- [Acceptable use of user licenses](https://docs.gitlab.com/legal/licensing_policy/)
 |
| Readable Markdown | - Tier: Free, Premium, Ultimate
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
Use the [`rules`](https://docs.gitlab.com/ci/yaml/#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` examples
The following example uses `if` to define that the job runs in only two specific cases:
yaml
```
job:
script: echo "Hello, Rules!"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
allow_failure: true
- if: $CI_PIPELINE_SOURCE == "schedule"
```
- If the pipeline is for a merge request, the first rule matches, and the job is added to the merge request pipeline with attributes of:
- `when: manual` (manual job)
- `allow_failure: true` (the pipeline continues running even if the manual job is not run)
- If the pipeline is not for a merge request, the first rule doesn’t match, and the second rule is evaluated.
- If the pipeline is a scheduled pipeline, the second rule matches, and the job is added to the scheduled pipeline. No attributes were defined, so it is added with:
- `when: on_success` (default)
- `allow_failure: false` (default)
- In all other cases, no rules match, so the job is not added to any other pipeline.
Alternatively, you can define a set of rules to exclude jobs in a few cases, but run them in all other cases:
yaml
```
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
```
- If the pipeline is for a merge request, the job is not added to the pipeline.
- If the pipeline is a scheduled pipeline, the job is not added to the pipeline.
- In all other cases, the job is added to the pipeline, with `when: on_success`.
If you use a `when` clause as the final rule (not including `when: 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](https://docs.gitlab.com/ci/jobs/job_rules/#avoid-duplicate-pipelines) for more details.
### Run jobs for scheduled pipelines
You can configure a job to be executed only when the pipeline has been scheduled. For example:
yaml
```
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.
### Skip jobs if the branch is empty
Use [`rules:changes:compare_to`](https://docs.gitlab.com/ci/yaml/#ruleschangescompare_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:
- Doesn’t have changed files, the job doesn’t run.
- Has changed files, the job runs.
For example, in a project with `main` as the default branch:
yaml
```
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.
## Run a job when a file is not present
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:
yaml
```
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.
## Common `if` clauses with predefined variables
`rules:if` clauses are commonly used with [predefined CI/CD variables](https://docs.gitlab.com/ci/variables/predefined_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.
yaml
```
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.
yaml
```
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`.
### Run jobs only in specific pipeline types
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:
- Branch pipelines that run for Git `push` events to a branch, like new commits or tags.
- Tag pipelines that run only when a new Git tag is pushed to a branch.
- Merge request pipelines that run for changes to a merge request, like new commits or selecting **Run pipeline** in a merge request’s pipelines tab.
- Scheduled pipelines.
| 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:
yaml
```
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 variable
Use 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](https://docs.gitlab.com/api/pipelines/#create-a-new-pipeline). |
| `chat` | For pipelines created by using a [GitLab ChatOps](https://docs.gitlab.com/ci/chatops/) command. |
| `external` | When you use CI services other than GitLab. |
| `external_pull_request_event` | When an [external pull request on GitHub](https://docs.gitlab.com/ci/ci_cd_for_external_repos/#pipelines-for-external-pull-requests) is created or updated. |
| `merge_request_event` | For pipelines created when a merge request is created or updated. Required to enable [merge request pipelines](https://docs.gitlab.com/ci/pipelines/merge_request_pipelines/), [merged results pipelines](https://docs.gitlab.com/ci/pipelines/merged_results_pipelines/), and [merge trains](https://docs.gitlab.com/ci/pipelines/merge_trains/). |
| `ondemand_dast_scan` | For [DAST on-demand scan](https://docs.gitlab.com/user/application_security/dast/on-demand_scan/) pipelines. |
| `ondemand_dast_validation` | For [DAST on-demand validation](https://docs.gitlab.com/user/application_security/dast/profiles/#site-profile-validation) pipelines |
| `parent_pipeline` | For pipelines triggered by a [parent/child pipeline](https://docs.gitlab.com/ci/pipelines/downstream_pipelines/#parent-child-pipelines). Use this pipeline source in the child pipeline configuration so that it can be triggered by the parent pipeline. |
| `pipeline` | For [multi-project pipelines](https://docs.gitlab.com/ci/pipelines/downstream_pipelines/#multi-project-pipelines). |
| `push` | For pipelines triggered by a Git push event, including for branches and tags. |
| `schedule` | For [scheduled pipelines](https://docs.gitlab.com/ci/pipelines/schedules/). |
| `security_orchestration_policy` | For [scheduled scan execution policies](https://docs.gitlab.com/user/application_security/policies/scan_execution_policies/) pipelines. |
| `trigger` | For pipelines created by using a [trigger token](https://docs.gitlab.com/ci/triggers/#configure-cicd-jobs-to-run-in-triggered-pipelines). |
| `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](https://docs.gitlab.com/user/project/web_ide/). |
These values are the same as returned for the `source` parameter when using the [pipelines API endpoint](https://docs.gitlab.com/api/pipelines/#list-project-pipelines).
## Complex rules
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:
yaml
```
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.
yaml
```
job1:
script:
- echo This rule uses parentheses.
rules:
- if: ($CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_BRANCH == "develop") && $MY_VARIABLE
```
## Avoid duplicate pipelines
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:
yaml
```
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`](https://docs.gitlab.com/ci/yaml/#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:
yaml
```
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](https://docs.gitlab.com/ci/debugging/#pipeline-warnings).
For example, the following does not trigger double pipelines, but is not recommended without `workflow: rules`:
yaml
```
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](https://docs.gitlab.com/ci/yaml/workflow/#switch-between-branch-pipelines-and-merge-request-pipelines):
yaml
```
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:
yaml
```
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`](https://docs.gitlab.com/ci/yaml/deprecated_keywords/#only--except), so `job-with-no-rules` runs in all cases except merge requests.
## Reuse rules in different jobs
Use [`!reference` tags](https://docs.gitlab.com/ci/yaml/yaml_optimization/#reference-tags) to reuse rules in different jobs. You can combine `!reference` rules with rules defined in the job. For example:
yaml
```
.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."
```
## CI/CD variable expressions
Use variable expressions with [`rules:if`](https://docs.gitlab.com/ci/yaml/#rulesif) 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_2`
- `if: $VARIABLE_1 != $VARIABLE_2`
You can compare a variable to the `null` keyword to see if it is defined. For example:
- `if: $VARIABLE == null`
- `if: $VARIABLE != null`
You 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: $VARIABLE`
You can also [use CI/CD inputs in variable expressions](https://docs.gitlab.com/ci/inputs/examples/#use-cicd-inputs-in-variable-expressions).
### Compare a variable to a regular expression
You can do regular expression matching on variable values with the `=~` and `!~` operators.
Expressions evaluate as `true` if:
- Matches are found when using `=~`.
- Matches are not found when using `!~`.
For example:
- `if: $VARIABLE =~ /^content.*/`
- `if: $VARIABLE !~ /^content.*/`
Additionally:
- Single-character regular expressions, like `/./`, are not supported and produce an `invalid expression syntax` error.
- Pattern matching is case-sensitive by default. Use the `i` flag modifier to make a pattern case-insensitive. For example: `/pattern/i`.
- Only the tag or branch name can be matched by a regular expression. The repository path, if given, is always matched literally.
- The entire pattern must be surrounded by `/`. For example, you can’t use `issue-/.*/` to match all tag names or branch names that begin with `issue-`, but you can use `/issue-.*/`.
- The `@` 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`.
- Use anchors `^` 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`.
- Variable pattern matching with regular expressions uses the [RE2 regular expression syntax](https://github.com/google/re2/wiki/Syntax).
### Store a regular expression in a variable
Variables on the right side of `=~` and `!~` expressions are evaluated as regular expressions. The regular expression must be enclosed in forward slashes (`/`). For example:
yaml
```
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:
yaml
```
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'
```
### Join variable expressions together
You can join multiple expressions using `&&` (and) or `||` (or), for example:
- `$VARIABLE1 =~ /^content.*/ && $VARIABLE2 == "something"`
- `$VARIABLE1 =~ /^content.*/ && $VARIABLE2 =~ /thing$/ && $VARIABLE3`
- `$VARIABLE1 =~ /^content.*/ || $VARIABLE2 =~ /thing$/ && $VARIABLE3`
You 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)`
## Migrate from `only` or `except` to `rules`
Use `rules` and CI/CD variable expressions to reproduce the same behavior as the deprecated [`only` and `except` keywords](https://docs.gitlab.com/ci/yaml/deprecated_keywords/#only--except).
For example, starting with this deprecated configuration:
yaml
```
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:
- The branch is the default branch (`main`).
- The branch name matches the pattern `/^stable-branch.*$/`.
- The pipeline runs on a schedule.
- `job2` uses `except` to skip pipelines when:
- The branch is the default branch (`main`).
- The branch name matches the pattern `/^issue-.*$/`.
- The pipeline is a merge request pipeline.
To create similar pipeline configuration with `rules`, use CI/CD variable expressions. For example, for a direct migration from `only` and `except` to `rules`:
yaml
```
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:
yaml
```
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.
## Troubleshooting
### Unexpected behavior from regular expression matching with `=~`
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. |
| Shard | 184 (laksa) |
| Root Hash | 17323278190169633584 |
| Unparsed URL | com,gitlab!docs,/ci/jobs/job_rules/ s443 |