Chapter 02Lesson 01~105 minutes

.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.

Configuration compilation.gitlab-ci.ymlDefaults & inheritIncludesCI Lint

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, and include without 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.
Availability baseline. The Chapter 02 learning path uses only core GitLab CI/CD configuration, local includes, Pipeline Editor/CI Lint, and optional current 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?

Chapter 02 rule: treat the compiled/expanded configuration as evidence. Review the source files, but diagnose what GitLab actually merged and evaluated.

2. A compiler-like mental model for GitLab CI/CD

Configuration becomes executable in stages
            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.

Migration note: current GitLab documentation deprecates defining 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.

Snapshot behavior: included configuration is resolved when a pipeline is created and the resulting configuration is stored for that pipeline. Rerunning a job does not refetch includes. Starting a new pipeline resolves them again. This is why pipeline ID + source SHA + configuration identity belong together in evidence.

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
Do not create a personal access token just to complete this chapter. Use an already authenticated disposable lab context, the Pipeline Editor, or the no-runner validation path. Never paste authentication headers or token values into lesson evidence.

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.

Next lesson

Guided Hands-On Workflow and Core Operations

Build the configuration incrementally, inspect the compiled result, inject structural and semantic failures, and classify exactly which validation layer detects each one.

Knowledge check

Why can a YAML parser report success while GitLab CI Lint reports failure?

If a job defines its own retry and default also defines retry, are the two values merged?

What evidence is most useful when an included file appears to change a job unexpectedly?

A configuration validates, but no pipeline appears after a source event. Which layer should you investigate first?

Does retrying one job refetch the include files?

Official references and version notes

Version and compatibility note

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.