Skip to content

Backends and transports

You declare settings. The role decides which transport carries each one. This page explains that decision, so a skip or a routing failure reads as a statement about your configuration rather than a surprise.

A preference, not a switch

gitlab_settings_backend is a preference. Each operation is applied through the first transport in that preference which is capable of the resource — capability being a static, per-resource fact recorded in the registry, not something discovered at runtime.

Two conditions must both hold for a transport to carry an operation:

  1. It is capable of that resource.
  2. Its channel is ready for the target — credentials present for the API transports, a reachable host or toolbox pod for Rails.

Routing never bypasses an unavailable preferred channel to silently use a different one. Changing transport can change which authority performs the work, so the role refuses rather than substituting.

Presets

Value Expands to Use when
api (default) [rest, graphql] Production automation. Protocol-agnostic: you never choose REST or GraphQL, only that a setting is applied. Pairs naturally with strict mode.
all [rest, graphql, rails] You need Rails-only surfaces. API-capable operations still take their earlier API transport. Pairs naturally with compatible mode.

A single transport (rest, graphql, rails) or an explicit ordered list ([rails, rest]) is also accepted. Order is significant — the first capable, ready transport owns the operation.

The transports

=== "REST"

Normalizes desired state into ordered resource operations, then reconciles
each with read-before-write: full pagination, identity matching,
type-tolerant comparison, transient-failure retries, and real `--check` and
`--diff` support. Secret fields travel separately from displayable
operation data.

=== "GraphQL"

Reconciles the committed GraphQL-served resources — compliance frameworks,
on-call schedules, security policies, and others listed in the
[capability matrix](generated-capability-matrix.md). Operations run against
committed, schema-validated documents, never strings built at runtime.

=== "Rails"

Stages a role-owned bulk executor and one JSON document of pre-planned
operations on the GitLab host, then invokes `gitlab-rails runner` **once**
per apply — one Rails boot regardless of item count. See
[Rails execution](rails-execution.md).

REST versus GraphQL is invisible to you: the two API surfaces are interchangeable for the settings the role manages. Rails is always an explicit opt-in, because it executes code on the instance.

When an operation cannot be routed

An operation is unroutable when every transport capable of it is absent from your preference, or when the selected transport's channel is not ready.

What happens then depends on gitlab_settings_mode:

Mode Behaviour
strict (default) The run fails, naming the operation and the reason. Strict success means every declared operation had a route to convergence.
compatible The operation is recorded as one skip with an actionable reason, and the run continues.

A skip reason names only transports — never a value from your settings. For example: a resource capable only of rails when your preference is api.

The narrow escape hatch

gitlab_settings_allow_unavailable_transports: true permits a recorded strict-mode skip in exactly one case: a transport you did select whose channel is unavailable.

It does not permit omitting every capable transport from your preference. That remains a strict failure, because it means you asked for work you never enabled a way to perform.

Dependencies may cross partitions forward, never backward

Transport partitions apply in a fixed order — Rails, then REST, then GraphQL — with every check-mode preflight completing before any partition writes. A dependent operation may therefore rely on a parent in an earlier-applying partition: a group created over REST can own a GraphQL child (a custom emoji, an audit streaming destination) in the same apply. Only a plan that would run the parent after its dependent is rejected.

  • Strict: such a plan fails rather than executing incompletely.
  • Compatible: the dependent operation is skipped, and the skip propagates to anything depending on it.

The reason names the parent operation, its transport, and that its partition applies after the dependent's.

The coverage manifest

Every apply returns a coverage list binding each planned operation — by id, resource, and target — to either the backend that processed it or its skip reason. Nothing is silently dropped:

coverage:
  - id: "1"
    resource: group
    target: platform
    backend: rest
  - id: "2"
    resource: group_saml_provider
    target: platform
    skipped: true
    reason: "requires the rails transport; add rails (or a preset that includes it) to backend"

Use it as the audit record of a run. In compatible mode it is also the list of what you still need to arrange a transport for.

Choosing a combination

Situation Backend Mode
Production automation over the API api strict
You need Rails-only surfaces and cannot fail the run all compatible
Ephemeral lab, maximum surface all compatible
Proving a specific transport works explicit list strict

Strict mode is the default deliberately: a run that quietly skipped half your desired state, and reported success, is worse than one that stopped.