Pipeline Presets

Define reusable pipeline configurations in .glciconfig.toml and run them with -p:

[pipelines.quick]
stages = ["lint", "test"]
context = "merge_request"
env = { CI_QUICK = "1" }
concurrency = 4

[pipelines.quick.skip]
jobs = ["slow-integration"]
job_patterns = [".*windows.*"]

[pipelines.full]
no_skip_all = true
manual_all = true
concurrency = 0

Usage#

glci run -p quick              # run only lint + test, skip windows jobs
glci run -p full               # run everything, including manual jobs
glci show -p quick             # visualize what the preset would run
glci run -p quick --skip extra # CLI flags are additive with preset

Preset fields#

FieldDescription
extendsInherit from another preset by name
stagesOnly run jobs in these stages (additive with jobs)
jobsOnly run these specific jobs, supports globs (additive with stages)
contextCI context (branch, merge_request, tag) or named context preset
mr_source, mr_targetMR source/target branches
envVariable overrides (key-value map)
concurrencyMax parallel jobs (0 = unlimited)
imageDefault Docker image
executorExecutor type (docker, kubernetes)
no_skip_allDisable all top-level skip rules
manual_allAuto-run all manual jobs
no_manualSkip manual jobs
dirtyInclude uncommitted files (pointer: not set = inherit default)

Composable presets with extends#

Use extends to build tiered presets where each tier inherits from the previous one:

[pipelines.tier1]
jobs = ["lint-yaml", "haml-lint", "qa:selectors"]
context = "merge_request"

[pipelines.tier2]
extends = "tier1"
jobs = ["rubocop", "eslint-changed-files"]

[pipelines.tier3]
extends = "tier2"
jobs = ["static-analysis 1/2", "static-analysis 2/2"]

Running glci run -p tier3 includes all jobs from tier1, tier2, and tier3.

Merge semantics:

Field typeBehavior
jobs, stagesAdditive — union of parent and child (deduplicated)
skip, manual, tokenMerged — lists are additive, child booleans override
envMerged — child keys override parent keys on conflict
Scalars (context, image, executor, etc.)Child wins if set, otherwise inherited from parent
Boolean flags (no_skip_all, manual_all, etc.)Either layer setting true enables the flag
dirtyChild wins if set, otherwise inherited from parent

Constraints:

Skip rules in presets#

Each preset can define its own skip rules, which are additive with the top-level [skip] section:

[pipelines.fast.skip]
jobs = ["slow-integration", "e2e-full"]
job_patterns = [".*windows.*"]
stages = ["deploy"]

Manual rules in presets#

Control which manual jobs auto-run:

[pipelines.deploy.manual]
auto_run = true                    # auto-run ALL manual jobs
jobs = ["deploy-staging"]          # or specific job names
job_patterns = ["deploy-.*"]       # or regex patterns

Token forwarding in presets#

Override token forwarding rules per preset:

[pipelines.deploy.token]
forward_host_token = true
stages = ["deploy"]
jobs = ["release"]
job_patterns = [".*-publish$"]

Glob patterns#

The jobs field supports * as a glob metacharacter:

[pipelines.tier3]
jobs = [
  "rubocop",                   # exact match
  "lint-*-yaml",               # matches "lint-doc-yaml", "lint-ci-yaml", ...
  "docs-lint *",               # matches "docs-lint 1/4", "docs-lint 2/4", ...
  "qa:*",                      # matches "qa:selectors", "qa:internal-links", ...
  "static-analysis *",         # matches all parallel shards
]
context = "merge_request"

Globs also work in CLI positional args (quote to prevent shell expansion):

glci run 'qa:*'
glci run 'static-analysis *'

Only * is a glob metacharacter. ?, [, ] are literal so matrix job names like test: [ruby 3.0] work as exact matches. Entries without * are exact matches with O(1) lookup.

Full example#

A more complete preset showing all available sections:

[pipelines.deploy-staging]
stages = ["build", "test", "deploy"]
context = "branch=staging"
mr_source = "feature/deploy"
mr_target = "staging"
env = { DEPLOY_ENV = "staging", CI_DEBUG = "1" }
concurrency = 2
image = "ruby:3.2"
no_skip_all = true
manual_all = true
dirty = false

[pipelines.deploy-staging.skip]
jobs = ["e2e-windows"]

[pipelines.deploy-staging.manual]
jobs = ["deploy-staging"]

[pipelines.deploy-staging.token]
stages = ["deploy"]

Viewing effective config#

Use glci config to see how your presets are resolved after merging global and project configs:

glci config             # show merged config including all presets
glci config --network   # show network/TLS settings
glci config --gitlab    # show resolved GitLab instance config

Additive stages + jobs#

When both stages and jobs are set in a preset, they are combined as a union: the resulting job set includes every job that matches either filter. This makes it easy to start with a broad stage and pull in individual jobs from other stages:

[pipelines.lint-plus]
stages = ["lint"]                              # all lint-stage jobs
jobs = ["workhorse:verify: [1.25]"]            # plus this one from the test stage

[pipelines.lint-plus.skip]
jobs = ["semgrep-sast", "secret_detection"]    # minus the heavy ones

The resolved set is (jobs in lint stage) ∪ (workhorse:verify: [1.25]) − skip.

When only one of stages or jobs is set, it acts as a standalone filter (unchanged behavior). skip always subtracts from the resolved set regardless of how a job was included.

Merging behavior#

Preset skip/manual/token rules are additive with top-level [skip]/[manual]/[token]. CLI flags (--stage, --skip, --env, etc.) override preset scalar values and are additive for list values.

The precedence for scalar settings is: CLI flag > preset > parent preset > global config > default. For list settings (skip jobs, manual jobs, etc.), all layers are merged together.

When using extends, the full inheritance chain is resolved before merging with top-level config and CLI flags.

Esc