TT Lab
Get started
Learn Learning paths Courses

GitLab CI/CD

rules Decides Creation, Not Execution

Continue in TT Lab

In one sentence

rules is not a device that asks "should I run or not" when a job starts, but a device that decides "should this job go on the list" at the moment the pipeline is created.

Why this was needed

The starting point of the problem is that one pipeline has to handle several situations. On a feature branch you want to run only the tests, in a merge request you want to add a security scan on top, on the default branch you want to go out to staging, and when a tag is attached you want to prepare a production deployment but have a person press the button. If you split these four into four files, the common part splits into four copies and soon they differ from one another.

The old syntax only/except took on this need halfway. You could list conditions, but you could not attach a different behavior to each condition (automatic run, manual approval, allowing failure), and you could not use the two keys together on one job. rules is the syntax that removed this limit by tying the condition and the behavior into a single entry, and now there is no reason to use only/except in new configuration. If you mix the two on one job, GitLab rejects the configuration.

How it works

rules is a list of entries, and it reads from the top, applies only the first entry whose condition matches, and then stops. This "first match only" property is everything. So an entry without a condition always matches, and whatever you write below it is never read. If you put a when: never without a condition in the middle of the list, all the rules after it die, and since this incident raises no error at all, it makes you fail for a long time to find out why the deployment does not go out.

An entry can have three kinds of conditions. if is a variable expression and is written like $CI_COMMIT_BRANCH == "main". changes looks at whether a particular path is included in this change. exists looks at whether a particular file exists in the repository.

The behavior when matched is decided by when. on_success runs automatically if everything before succeeded, manual creates the job but requires a person to press it to start, always runs even if the earlier ones failed, and never means the job is not created at all. When you use manual, the allow_failure that goes with it has a meaning that is easy to confuse, and it must be false for that manual job to be a real approval gate. If it is true, the pipeline ends in success even if nobody presses it.

What we should re-confirm here is the evaluation time. rules is evaluated once when the pipeline is created, and is not looked at again when the job starts. So you cannot decide whether a later job runs from a value that an earlier job made during its run. Such conditional branching must be handled not in rules but in the job's script.

What you see in the field

The most common symptom is two pipelines being created for one commit. It is because a branch pipeline and a merge request pipeline both match the same condition, and it eats twice the runner resources and gives two status indicators. The fix is not to add conditions to each job but to block it, with workflow:rules, so that only one pipeline itself is created.

The second most common is putting a production deployment job at when: manual and leaving allow_failure to its default. You believed it was an approval gate, but one day you see a pipeline that nobody pressed ending in green.

What to look at next

We look at artifacts, which hands files between jobs, and cache, which saves time between runs. The names are similar and they are often mixed up, but they are completely different things.