TT Lab
Get started
Learn Learning paths Courses

GitLab CI/CD

Why Pipelines Moved Into a File

Continue in TT Lab

In one sentence

.gitlab-ci.yml is the file that brings the build server's configuration into the repository, so that it gets the same review, the same history, and the same revert as the code.

Why this was needed

The incident in the days when build servers were configured through a screen usually ended with one sentence. "It worked until yesterday, it doesn't today, and nobody knows what changed." This is because the job configuration lived in a database outside the repository. That configuration has no diff, no review, no branch, and no commit to revert. The fact that someone turned off one checkbox comes out only after the incident, and then only by relying on memory.

The deeper problem was the absence of branches. Code differs from branch to branch, but there was only one pipeline, so to add a new test stage you had to change the builds of all branches at once. So people stopped touching the pipeline, and the pipeline hardened as someone had made it years ago. A hardened pipeline soon becomes a pipeline nobody trusts.

Moving the configuration into a file inside the repository solves these four things at once. Each branch can have its own pipeline, the pipeline change itself can be reviewed in a merge request, when something goes wrong, reverting the commit brings the pipeline back with it, and what changed, when, and why stays in the Git log.

How it works

GitLab reads the .gitlab-ci.yml at the repository root and creates one pipeline. What matters here is the order. When a commit comes in, GitLab first reads the configuration file in that commit, decides which jobs to create from its content, and then hands the created jobs to runners. That is, the configuration is fully evaluated before any job starts. This order is also why rules, which you will learn later, decides not "should this job run" but "should this job be created".

The file's syntax is a single YAML mapping. One top-level key is one job, and only a few reserved names (stages, variables, default, include, workflow) are used as global settings rather than jobs. This simplicity is both the strength and the trap. If you put in one wrong space of indentation, the job disappears entirely, yet YAML itself raises no error. The file parses fine, and that job merely becomes a child of another key.

Let us make the scope of this course clear from the start. This lab environment has neither a GitLab server nor a GitLab Runner. So you cannot see a pipeline actually running, and we do not bring up a fake runner that pretends to. Instead, we cover the configuration language and its execution model — which jobs get created, in what order they start, and what waits for what. If you install a tool, what you can learn is only the tool, but the execution model can be learned without a runner and remains even when the vendor changes.

What you see in the field

The first thing that changes in a team that has moved its configuration into a file is the review conversation. The question "why did you move this test after the deployment" stays as a merge request comment, and its answer stays with it. Conversely, in a team that still configures through a screen, the same question goes back and forth in chat and disappears.

Another scene you often see is starting to look for include and templates only after the pipeline file has swollen to hundreds of lines. If the file is inside the repository, the bloat is visible, so refactoring begins. If it is inside a screen, nobody sees its size.

What to look at in the next article

We take apart what a single job really contains, key by key. We look at why a job without script is not a job, why a single dot in front of the name excludes it from execution, and how default and extends reduce duplication in different ways.