TT Lab
Get started
Learn Learning paths Courses

GitLab CI/CD

What One Job Contains

Continue in TT Lab

In one sentence

A job is one top-level key, and within it the only thing that creates execution is script; the other keys are all decoration that decides "when, where, and carrying what" that script runs.

Why this was needed

The most common misconception of someone reading pipeline configuration for the first time is thinking that memorizing key names is enough. But the place where people actually get stuck is not the syntax but the structure. You wrote stage and the job doesn't show up, you wrote image and a different image is used, and you copied the same five lines into six jobs and then fixed only one place and caused an incident. The cause in all three cases is the same — not knowing what is a job and what is not, and where values flow in from.

How it works

One key of the top-level mapping is one job. There are three exceptions, though. First, stages, variables, default, include, and workflow are reserved global settings, so they are not jobs. Second, a key whose name starts with a dot (such as .python-base) is a hidden job, so it is not put on the pipeline as something to execute. Third, if the value is not a mapping, it cannot become a job.

What makes a job a job is script. A job without script is a configuration error, and GitLab does not create such a pipeline at all. This is not a trivial rule but a design intent — a job with nothing to execute has no reason to exist.

If you group the remaining keys into three branches, there is less to memorize. Where it runs is decided by image, services, and tags. When it runs is decided by stage, needs, and rules. What it runs carrying is decided by variables, artifacts, cache, and before_script. All the keys you will touch in the labs are within these three branches.

There are two devices for reducing duplication, but they differ in nature. default is the floor value applied to a job that decided nothing. It is global, and if a job writes the same key directly, that one wins. extends brings in the specified fragment with a deep merge. So reuse works in a way like putting before_script in a fragment and writing only script differently in the job. The seemingly similar YAML anchors (& and *) are not a merge but an expansion of the whole thing in place, and they are valid only inside one document, so they cannot be used between files brought in with include. That is why real-world templates use extends, not anchors.

Incidents that often happen in practice

The most common incident is indentation. If you put script one level too deep, it becomes not a key of the job but a child item of the preceding key, and the job becomes a job without script, so it is rejected by configuration validation or quietly disappears from the list. The fact that YAML complains about nothing is what makes this incident drag on.

The second most common is forgetting the dot when making a fragment. If you write python-base, it is not a fragment to hand down but a job that actually runs in every pipeline. Usually it has no script, so it is caught as a configuration error, but if you had put a script in the fragment, it runs normally and one meaningless job keeps being executed.

What you will do in the next lab

With a single piece of configuration, you answer for yourself what is a job and what is not, which value wins among variables of the same name, and which job runs in each situation. You see in that file the points where what is a floor value and what is a merge diverge.