.gitlab-ci.yml Structure, Pipeline Compilation, Keywords, Defaults, and Configuration Validation: Concepts, Architecture, and Mental Model
A GitLab pipeline does not execute the text of
.gitlab-ci.yml directly. GitLab first resolves
includes, applies global defaults and inheritance rules, validates
the resulting configuration, evaluates pipeline-creation logic, and
materializes the jobs that become the executable graph. This lesson
builds that compiler-like mental model so configuration problems can
be diagnosed before they are misclassified as runner or script
failures.
Learning objectives
- Explain the path from repository CI configuration at an exact revision to a merged/expanded configuration and then to pipeline/job creation.
-
Distinguish global keywords, job keywords,
default,inherit, andincludewithout treating YAML as a keyword catalog. - Explain why default keyword values are copied to jobs only when the job does not already define that keyword.
- Use CI Lint, pipeline simulation, and the Pipeline Editor Full configuration view as different evidence surfaces.
- Recognize deprecated configuration patterns and record version-sensitive assumptions before building production policy around them.
glab inspection. No
runner is required to learn compilation and validation; execution is
an optional evidence layer when disposable compute is available.
1. The problem: YAML text is not yet an executable pipeline
Chapter 01 separated source, pipeline, job, runner, artifact, and deployment state. Chapter 02 moves one layer earlier: before GitLab can create a pipeline, it has to turn repository configuration into a coherent internal configuration. A file can be valid YAML yet invalid GitLab CI/CD. It can be valid GitLab CI/CD yet produce no pipeline for a particular source. It can also compile successfully and still fail later because a job command, runner, credential, or external service is wrong.
That means “the YAML looks fine” is not a diagnosis. The useful question is: which configuration transformation has been proven, and which one has not?
2. A compiler-like mental model for GitLab CI/CD
flowchart TD
A[Repository revision + .gitlab-ci.yml] --> B[Resolve includes and inputs]
B --> C[Merge configuration]
C --> D[Apply defaults and inheritance]
D --> E[Validate GitLab CI schema and semantics]
E --> F[workflow/rules pipeline creation decision]
F --> G[Materialize final job definitions]
G --> H[Queue eligible jobs for runners]
The word “compile” is useful as a mental model even though this is not a traditional machine-code compiler. GitLab resolves imported configuration, combines it according to merge rules, applies inheritance, validates the result, and evaluates pipeline/job inclusion logic. Each arrow can fail independently or change the final graph.
3. Global configuration and job configuration have different roles
| Configuration object | Scope | What it controls | Evidence |
|---|---|---|---|
default
|
Pipeline configuration | Supported defaults copied into jobs that do not define that keyword. | Full configuration / lint output. |
include
|
Pipeline configuration | Imports and merges additional CI configuration before the main file is merged. | Included-file tree and expanded configuration. |
workflow
|
Pipeline creation | Can decide whether a pipeline is created for the current source. | Simulation / pipeline creation result. |
| Job | Executable graph |
Defines script and job-scoped execution
configuration.
|
Expanded job + eventual job record. |
inherit
|
Job | Controls which defaults/global variables a job receives. | Expanded job definition. |
GitLab also has many other global and job keywords. This chapter intentionally introduces only enough vocabulary to reason about compilation. Later chapters teach jobs, variables, rules, reuse, artifacts, and deployment concerns in depth.
4. Defaults are copied, not recursively merged into a job keyword
A default section is a way to express supported job
defaults once. If a job does not define a particular
default-supported keyword, GitLab copies that default into the job.
If the job already defines that keyword, the job value takes
precedence; GitLab does not recursively merge the default value into
that job keyword.
default:
retry: 1
interruptible: true
verify:
script:
- echo "verify"
Here verify receives both retry: 1 and
interruptible: true. If the job later defines its own
retry, that job value is used instead of the default
retry. This distinction becomes especially important
for structured keywords such as artifacts and caches.
image, services,
cache, before_script, and
after_script globally. Put supported global job
defaults under default instead.
5. Inheritance is explicit configuration state
default:
before_script:
- echo "chapter02 preflight"
retry: 1
normal_job:
script:
- echo "inherits defaults"
standalone_job:
inherit:
default: false
script:
- echo "does not inherit defaults"
normal_job receives the supported defaults.
standalone_job does not. This is not merely style: the
expanded job definitions are different executable contracts. When
inheritance is surprising, inspect the expanded configuration rather
than guessing from the root file.
6. Includes create a configuration graph, then GitLab takes a snapshot
include:
- local: /.gitlab/ci/base.yml
default:
retry: 1
build:
script:
- echo "build $CI_COMMIT_SHA"
A local include stays in the same repository and revision, which makes it ideal for early learning. GitLab resolves included files, recursively resolves nested includes, merges included configuration in order, then merges the main configuration last. When overlapping values are mappings, GitLab performs a deep merge; when a value is not a mapping, the later value replaces the earlier one.
7. Validation has layers; each proves something different
| Layer | Useful surface | What it proves | What it does not prove |
|---|---|---|---|
| YAML syntax | Editor/YAML parser | The document is parseable YAML. | That GitLab accepts CI keywords. |
| GitLab static validation | CI Lint / editor | GitLab accepts schema/logic and resolves includes in context. | That the source/ref creates the intended pipeline graph. |
| Expanded configuration |
Full configuration / glab ci config compile
|
What includes/inheritance/references became after expansion. | That jobs execute successfully. |
| Pipeline simulation |
CI Lint simulation / glab ci lint --dry-run
|
A default-branch push simulation can create the expected graph and can expose rule/needs issues. | That a runner is available or scripts succeed. |
| Runtime execution | Pipeline/job evidence | Runner executed commands for a concrete pipeline/SHA. | That downstream deployment or external health is correct. |
8. Configuration must be tied to an immutable revision
A branch name is convenient but movable. Preserve the commit SHA that contains the root CI file and every local include used by the pipeline. For cross-project or remote reuse, the trust problem is larger because configuration can come from another repository or URL. This chapter uses local includes; later reuse chapters expand the governance model.
git status --short --branch
git rev-parse HEAD
git show --stat --oneline HEAD
git diff -- .gitlab-ci.yml .gitlab/ci/
9. Read-only inspection before mutation
Before editing, record what currently exists. In GitLab, inspect
Build → Pipeline editor. The editor validates as
you type, can show included files, visualizes the graph, and exposes
Full configuration with includes and references
expanded. With current GitLab CLI tooling,
glab ci lint validates and
glab ci config compile displays compiled configuration.
glab auth status
git status --short --branch
git rev-parse HEAD
# Current documented CLI surfaces:
glab ci lint .gitlab-ci.yml
glab ci config compile
10. Foundation mistakes to eliminate now
- “Valid YAML means valid GitLab CI.” GitLab schema and semantic validation still have to pass.
- “A valid CI file means a pipeline will exist.” Pipeline creation rules can legitimately produce no pipeline.
-
“Defaults merge into whatever the job defines.” A
job-defined keyword replaces that default keyword rather than
receiving a recursive merge from
default. - “I can diagnose an include by reading only the root file.” Inspect the include graph and expanded configuration.
- “Rerunning a job refreshes remote/includes.” A job rerun keeps the pipeline’s stored configuration snapshot.
- “Deprecated means broken today.” Deprecated syntax can remain backward-compatible while still being a migration risk; record warnings and migrate deliberately.
11. Micro-lab: predict the compiled result before running anything
# .gitlab/ci/base.yml
default:
retry: 1
interruptible: true
# .gitlab-ci.yml
include:
- local: /.gitlab/ci/base.yml
verify:
script:
- echo "verify"
no_defaults:
inherit:
default: false
script:
- echo "standalone"
Before opening CI Lint, predict the final job definitions:
verify should inherit retry and
interruptible; no_defaults should inherit
neither. Then use Full configuration or
glab ci config compile to test the prediction. No
runner is needed.
Knowledge check
Why can a YAML parser report success while GitLab CI Lint reports failure?
A YAML parser checks YAML syntax only. CI Lint also checks GitLab CI/CD schema and logic, including contextual include resolution.
If a job defines its own retry and
default also defines retry, are the
two values merged?
No. The job-defined keyword takes precedence; the default value for that keyword is not merged into it.
What evidence is most useful when an included file appears to change a job unexpectedly?
Inspect the include graph and fully expanded/compiled configuration tied to the exact source revision.
A configuration validates, but no pipeline appears after a source event. Which layer should you investigate first?
Pipeline-creation logic such as workflow:rules and
source/ref context, not runner capacity.
Does retrying one job refetch the include files?
No. Jobs in the same pipeline use the stored configuration snapshot. A new pipeline resolves includes again.
Official references and version notes
-
CI/CD YAML syntax reference
— current global/job keyword semantics, including
default,inherit, andinclude. - Use CI/CD configuration from other files — include resolution, merge order, overrides, nesting, and snapshot behavior.
- Validate GitLab CI/CD configuration — CI Lint, syntax checking, and pipeline simulation.
- Pipeline editor — included-file tree, visualization, continuous validation, and Full configuration.
-
Deprecated CI/CD keywords
— current migration guidance, including global
image/services/cache/before_script/after_scriptand legacyonly/except. -
glab ci lintandglab ci config— current CLI surfaces for validation and compiled configuration inspection.
Version-sensitive statements in this chapter were rechecked
against current primary GitLab documentation on
2026-09-11. Core CI Lint, Pipeline Editor
validation, default, inherit, and local
includes are documented across GitLab Free, Premium, and Ultimate
on GitLab.com, GitLab Self-Managed, and GitLab Dedicated. The labs
deliberately avoid depending on paid policy features, production
credentials, external cloud accounts, or privileged runner
administration.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.