Chapter 02Lesson 03~100 minutes

.gitlab-ci.yml Structure, Pipeline Compilation, Keywords, Defaults, and Configuration Validation: Configuration, Design Choices, and Tradeoffs

Once a configuration is valid, design choices still determine whether it stays understandable. This lesson compares defaults with explicit job configuration, inheritance with local clarity, local includes with cross-project reuse, and static linting with pipeline simulation so teams can choose the smallest abstraction that preserves reviewability and evidence.

Design tradeoffsInheritanceReusable configImmutabilityValidation strategy

Learning objectives

  • Choose when a default reduces duplication and when explicit job configuration is safer for readers and reviewers.
  • Use inherit:default deliberately instead of assuming every job receives every global default.
  • Compare local, project, remote, template, and component reuse by trust boundary rather than convenience alone.
  • Explain snapshot behavior: included configuration is resolved when a pipeline is created and a job rerun does not refetch includes.
  • Select static lint, expanded-configuration inspection, simulation, or execution according to the question being answered.
Design principle: abstraction is useful only when it makes the effective pipeline easier to reason about. Every reusable layer should preserve reviewability, a version/trust boundary, and a way to inspect the compiled result.

1. Defaults versus explicit job configuration

Defaults reduce repetition when many jobs genuinely share one policy. They become harmful when a reader must mentally reconstruct many exceptions to understand a safety-critical job. A deployment job, privileged image-build job, or compliance job often benefits from more explicit configuration even when ordinary test jobs share defaults.

Choice Use it when Main risk Evidence to require
default Many jobs share the same supported keyword value. A later job override silently diverges from expectations. Expanded job definition.
Explicit job keyword The setting is safety-critical or exceptional. Duplication can drift across jobs. Direct source + compiled job.
inherit:default:false A job intentionally needs isolation from common defaults. Required bootstrap/security behavior may disappear. Compiled job plus a reason in review.
Selective inherit:default list A job needs only a subset of defaults. Complex inheritance becomes hard to audit. Expanded configuration diff.

2. Know the default copy boundary

A subtle but important rule: default keyword values are copied into a job only when that job has not defined the same keyword. They are not recursively merged with job-defined values. Therefore “put half the artifacts policy in default and half in the job” is not a safe mental model.

default:
  retry: 2
  interruptible: true

fast_test:
  retry: 0       # this job uses 0, not a merge with default retry
  script:
    - echo test

When reviewing a job, reason from the compiled job, not from “the defaults plus whatever seems missing.”

3. Explicit configuration versus inheritance

Inheritance trades local readability for centralized policy. The right balance depends on change frequency and consequence. A shared before_script that installs harmless test tooling may be fine as a default. A hidden inherited command that changes cloud authentication or deployment targets is much harder to review safely.

Question Prefer explicit Prefer inheritance/default
Is this setting security-sensitive? Usually. Only with strong centralized ownership and obvious compiled evidence.
Does almost every job use the same value? Maybe not. Yes, if exceptions are rare and visible.
Will reviewers inspect generated/expanded config? Helpful. Important as abstraction grows.
Does failure affect production? Bias explicit. Use inheritance only with documented controls.

4. Local simplicity versus reusable includes

An include changes the configuration trust graph. Local includes stay inside the same project/revision. Project includes cross a repository boundary and should use deliberate, reviewable refs. Remote includes cross into a URL trust boundary. CI/CD components add a versioned interface model and are covered deeply later in the course.

Reuse source Typical trust boundary Production guidance
include:local Same repository/revision. Best first extraction step; review with the same commit.
include:project Another GitLab project. Pin a reviewed immutable commit SHA or controlled release ref where practical; verify access and ownership.
include:remote External HTTP(S) resource. Treat as executable dependency; prefer integrity/version controls and a narrowly owned source.
include:component Versioned CI/CD component contract. Use explicit inputs/versioning; detailed design comes in Chapter 13.
Why immutability matters: a reusable configuration file can execute commands with the caller job’s authority. A moving branch or mutable remote resource can change pipeline behavior without a change to the consuming repository.

5. Merge order is part of the design

GitLab resolves nested includes recursively, merges included files in order, then merges the main configuration last. Main-file values can therefore override included configuration. Hash mappings can deep-merge; array-like/non-map values are replaced rather than appended item-by-item. This makes “override one command in a script array” a common source of surprises.

# included.yml
test:
  script:
    - install_dependencies
    - run_tests

# .gitlab-ci.yml
include:
  - local: /included.yml

test:
  script:
    - echo "replacement script"   # replaces the included script list

If you intend to retain the included commands, reproduce them or choose a different reuse mechanism. Verify in Full configuration instead of assuming list concatenation.

6. Static validation versus simulation versus execution

Question Best first surface Cost / side effect
Is the YAML/GitLab schema valid? CI Lint static validation. No runner; no job execution.
What did includes/inheritance become? Full configuration / glab ci config compile. No runner; read-only inspection.
Will a push-like context create the graph? Pipeline simulation / glab ci lint --dry-run. Server-side simulation; no job execution.
Can the job command actually run? Disposable pipeline/job. Consumes runner time and can have side effects.
Did deployment succeed? External target verification. Potentially consequential; separate from CI validation.

Use the cheapest evidence layer that can answer the current question, then move one layer deeper only when needed. This reduces feedback time and prevents unnecessary side effects during diagnosis.

7. Pipeline snapshots improve reproducibility—but new pipelines can differ

When GitLab creates a pipeline, included configuration is evaluated and stored as part of that pipeline’s configuration snapshot. A job rerun stays on that snapshot. A new pipeline resolves includes again. If an external include is mutable, two pipelines for the same application source can compile differently at different times.

Application source

Exact commit SHA of the consuming repository.

Configuration dependencies

Project/ref/file or remote/component identity for each include.

Compiled snapshot

Full/merged configuration associated with the pipeline.

Pipeline identity

Pipeline ID, source, ref, SHA, creation time.

8. Worked scenario: central test policy without hiding deployment behavior

A platform team wants common retry/interruption behavior for 40 service repositories, but each service owns its production deployment command. A reasonable design is to centralize low-risk test defaults in a versioned project include/component while keeping production deployment job configuration explicit in each service or a separately governed deployment component.

Concern Choice Reason
Retry policy for ordinary tests Central default Low-risk, broadly consistent behavior.
Production cloud identity Explicit/narrow component contract High consequence and trust-sensitive.
Remote include ref Immutable reviewed SHA/release Prevents silent behavior drift.
Validation Compile + simulate in MR Reviewers see effective configuration before merge.

9. Design anti-patterns

  • Inheritance as obscurity: a job’s behavior requires opening many files and guessing merge order.
  • One giant root file: avoiding every include even when stable domain boundaries are obvious.
  • Mutable central config: consuming main from a shared project as if it were an immutable release.
  • Lint-only confidence: treating static validation as proof that source-specific rules or runtime commands work.
  • Copy-paste forks: duplicating a shared policy into dozens of projects until fixes cannot propagate consistently.

10. Review checklist for a maintainable configuration

  • Can a reviewer identify the root file and every include without privileged access?
  • Are shared configurations pinned/versioned according to their trust level?
  • Can Full configuration explain the final job without guesswork?
  • Are inherit exceptions intentional and documented?
  • Are deprecated patterns being introduced, or only maintained temporarily for migration?
  • Is simulation used when pipeline source/rules affect whether jobs exist?
  • Are runtime side effects kept out of configuration-validation exercises?
Next lesson

Diagnostics, Failure Modes, Security, and Performance

Turn design principles into an evidence-first troubleshooting ladder for syntax errors, include failures, inheritance drift, deprecated constructs, and valid configurations that create no pipeline.

Knowledge check

Why might a deployment job be better kept explicit even when test jobs use global defaults?

What is the main trust difference between a local include and a project/remote include?

Does GitLab append a job script list to an included script list during merge?

When should you choose simulation over a real pipeline first?

Why preserve compiled configuration with pipeline identity?

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.