.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.
Learning objectives
- Choose when a default reduces duplication and when explicit job configuration is safer for readers and reviewers.
-
Use
inherit:defaultdeliberately 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.
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. |
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.
Exact commit SHA of the consuming repository.
Project/ref/file or remote/component identity for each include.
Full/merged configuration associated with the pipeline.
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
mainfrom 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
inheritexceptions 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?
Knowledge check
Why might a deployment job be better kept explicit even when test jobs use global defaults?
Deployment configuration is high consequence; local explicitness can make identity, target, and side effects easier to review and audit.
What is the main trust difference between a local include and a project/remote include?
A local include stays in the same repository/revision. Project/remote includes introduce an additional repository or URL dependency whose ownership and immutability must be governed.
Does GitLab append a job script list to an
included script list during merge?
No. Arrays/non-map values are replaced according to merge precedence rather than appended item-by-item.
When should you choose simulation over a real pipeline first?
When the question is whether configuration/rules would materialize the expected graph and you do not yet need runtime evidence.
Why preserve compiled configuration with pipeline identity?
It records what GitLab actually evaluated, including includes/inheritance, so later reruns or dependency changes can be distinguished from the original pipeline snapshot.
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.