CI/CD on GitLab
Intermediate GitLab UI
Why this matters
The pipeline is the gatekeeper on every merge request, and when it turns red the merge button locks. Most of the time the cause is your change and the fix takes a minute, but only if you can find it. This lesson is about reading the result; Section 14 is about the ideas behind it and writing the file.
The vocabulary, briefly
| Word | Means |
|---|---|
| Pipeline | Everything that runs for one push: all the stages and jobs |
| Stage | A phase, such as test or deploy; stages run in order |
| Job | One unit of work in a stage, such as tests or lint-docs; jobs in the same stage run in parallel |
| Runner | The machine that executes a job |
| Artifact | A file a job produces and keeps, such as a report or a built site |
The playground's pipeline has one stage, test, with two jobs, tests and lint-docs. A push runs both; if either fails, the pipeline is red.
Where you see it
| Place | Shows |
|---|---|
| The merge request's Overview | The pipeline's status and each job as a small icon |
| Build → Pipelines | Every pipeline for the project, with branch, commit and who triggered it |
| Build → Jobs | Every job, useful for finding a flaky one |
| A commit page | The pipeline for that commit |
Statuses: passed (green), failed (red), running (blue), pending (waiting for a runner), canceled, skipped, and warning for a job that failed but was marked allow_failure, which does not block the merge.
Reading a failure
The whole skill is four steps, and the third is where most people give up too early.
- Open the failed job. On the merge request, click the red icon; on the pipeline page, click the job's name. Do not read the pipeline page for the reason: it only says which job failed.
- Scroll to the bottom of the log. The last twenty lines almost always contain the cause. The top is setup noise: cloning, installing, versions.
- Find the first real error, not the last line. The last line is usually
ERROR: Job failed: exit code 1, which says only that something failed. The useful line is above it, often a file name and a line number. - Match it to your change. If the error names a file you touched, it is yours. If it names something you never opened, it may be a pre-existing failure or a flaky job; check whether the same job fails on
main.
A Markdown lint failure, which is the one a documentation contributor meets most, looks like this near the end of the log:
docs/getting-started.md:31 MD001/heading-increment Heading levels should only increment by one level at a time [Expected: h2; Actual: h3]
ERROR: Job failed: exit code 1File, line, rule, and what it expected. Fix that line, commit, push; the pipeline runs again automatically.
Retrying, and when it is legitimate
Each job has a Retry button, and the pipeline has Retry for all failed jobs. Retrying is right when the failure was the infrastructure: a timeout, a runner that vanished, a network error fetching a dependency. It is not right when the log names your file: the same code produces the same result, and retrying only wastes minutes.
A job that fails intermittently on unchanged code is flaky, and it deserves an issue rather than a habit of retrying.
Reading .gitlab-ci.yml
The pipeline is defined by a file in the repository, so you can read it without asking anyone. The playground's:
stages:
- test
tests:
stage: test
image: python:3.12
script:
- python -m unittest discover -s tests -v
lint-docs:
stage: test
image: node:20
script:
- npx --yes markdownlint-cli "**/*.md" --ignore node_modulesFour things to recognize:
stageslists the phases in order.- Each top-level name (
tests,lint-docs) is a job. imageis the environment the job runs in, which is why the pipeline's Python version can differ from yours (lesson 1.4).scriptis the commands, run in order. You can run these yourself, locally, before pushing — which is the single best way to avoid red pipelines.
That last point is worth acting on: the script lines are the contract. Running npx markdownlint-cli "**/*.md" locally takes ten seconds and catches what the pipeline would catch ten minutes later.
How to do it
There is no git pipeline command; pipelines are GitLab's. What the terminal gives you is the ability to run the same checks before pushing:
$ python3 -m unittest discover -s tests -v
$ npx --yes markdownlint-cli "**/*.md" --ignore node_modulesRead .gitlab-ci.yml and run its script lines. If they pass locally, the pipeline usually passes too.
The GitLab Workflow extension shows the pipeline status of the current branch in the status bar and can open the failing job in a browser. It also validates .gitlab-ci.yml as you edit it, which catches indentation mistakes before a push.
With a GitLab account configured, IntelliJ shows pipeline status on the branch and in the Merge Requests window. YAML editing gets schema validation for .gitlab-ci.yml in the Ultimate edition.
- On the merge request, the pipeline appears in Overview; click a red job icon to open its log.
- Build → Pipelines lists every run; click a pipeline to see its jobs as a graph.
- In a job log, use the search box for
error, and the raw-log button if you want to read it in a plain page. - Retry re-runs a job; Cancel stops a running one.
- Build → Pipeline schedules shows runs that happen on a timer rather than on a push, which explains a nightly failure nobody caused.
- Settings → CI/CD → Variables holds the values jobs need, masked and protected (lesson 8.6).
The same ideas under Actions: a workflow instead of a pipeline, defined in .github/workflows/*.yml, with jobs and steps, and runners that execute them. The Checks tab on a pull request is the equivalent of the pipeline panel. See lesson 10.10.
Common mistakes
- Reading only the last line of the log.
exit code 1is the symptom; the cause is above it. - Retrying a real failure. The same code fails the same way; read the log instead.
- Assuming a red pipeline is your fault. Check whether the same job fails on
main; a brokenmainis everyone's problem, not yours. - Not running the pipeline's own commands locally. They are written down in
.gitlab-ci.yml. - Pushing repeatedly to see whether it passes. Each attempt costs minutes and clutters the merge request.
Try it yourself
Goal: break the pipeline on purpose and fix it from the log.
- On a branch in your practice project, edit
docs/faq.mdand introduce a Markdown lint error: put a####heading directly under a##. - Commit and push, and open the merge request.
- When the pipeline turns red, open the failing job and find the line naming the file, line number and rule.
- Fix that line, commit, push, and watch the pipeline run again.
- Before the next push, run the job's own command locally and confirm it reports the same thing.
Expected result: the log names docs/faq.md, a line number and MD001; after the fix the pipeline is green; the local command agrees with the pipeline.
Show solution
Step 5 is the habit worth keeping. The pipeline's script lines are the exact checks, so running them locally turns a ten-minute round trip into a ten-second one. If your project's pipeline needs credentials you do not have, run the parts that do not, which is usually the linting.
Check yourself
Key terms
Pipeline Continuous integration (CI) Continuous delivery / deployment (CD) Artifact Logs (program output)