Skip to content

Rails execution

Some GitLab settings have no API. The Rails backend reaches them by running code on the instance — which is why it is never chosen for you.

This executes code on your GitLab instance

The Rails backend stages a script on the GitLab host and runs gitlab-rails runner. Enable it deliberately, scope its credentials tightly, and prefer the API transports for anything they can serve.

When you need it

Only when a resource you want to manage is capable of rails and nothing else. The capability matrix records which resources those are.

If a resource is API-capable, it stays on its API transport even with rails in your preference — adding Rails does not migrate work onto it.

Enabling it

Rails is not in the default preference. Add it:

gitlab_settings_backend: all        # [rest, graphql, rails]
gitlab_settings_mode: compatible

all pairs naturally with compatible mode: you are usually reaching for Rails because you want maximum surface, and a skip is more useful than a failure in that situation.

How a run works

  1. Stage the role-owned bulk executor (files/apply_settings.rb) and one JSON document of pre-planned operations onto the target.
  2. Invoke gitlab-rails runner exactly once per apply — one Rails boot regardless of how many operations are in the document.
  3. Remove the staged directory in an always block.

Staged directories are mode 0700.

The Ruby executor is a deliberately dumb dispatcher over the same operations the REST backend reconciles. All normalization lives controller-side in one implementation, so the two backends cannot drift apart in behaviour. See ADR-0003.

One boot per apply is why Rails is the structurally fast path for bulk configuration — a Rails boot costs seconds, and the role pays it once rather than per setting.

Strategies

Set rails_strategy on the target.

=== "kubernetes_toolbox (default)"

Executes through `kubernetes.core` modules against the GitLab chart's
toolbox pod.

```yaml
gitlab_settings_target:
  url: https://gitlab.example.com
  auth:
    type: personal_access_token
    token: "{{ vault_gitlab_token }}"
  rails_strategy: kubernetes_toolbox
  kubernetes:
    context: gitlab-lab
    namespace: gitlab
    toolbox_selector: app=toolbox,release=gitlab
```

| Field | Purpose |
|---|---|
| `kubeconfig` | Kubeconfig file. Omit to use the environment default. |
| `context` | Kubeconfig context. Omit to use the current context. |
| `namespace` | Namespace of the GitLab chart deployment. Default `gitlab`. |
| `container` | Toolbox container name. Default `toolbox`. |
| `toolbox_selector` | **Required.** Release-specific label selector locating exactly one toolbox pod. |

The role requires exactly one Running, Ready, non-terminating toolbox pod.
Zero or several is an error, not a pick-one situation.

=== "ssh_omnibus"

Executes over SSH against an Omnibus host in your inventory.

```yaml
gitlab_settings_target:
  url: https://gitlab.example.com
  auth:
    type: personal_access_token
    token: "{{ vault_gitlab_token }}"
  rails_strategy: ssh_omnibus
```

```yaml
gitlab_settings_rails_host: gitlab-primary
gitlab_settings_rails_become: true
```

`gitlab_settings_rails_host` is the inventory hostname of the Omnibus host
and is required for this strategy. SSH connectivity, credentials, jump
hosts, and host keys belong to that host's ordinary inventory
configuration — the role does not reimplement them.

`gitlab_settings_rails_become` escalates privileges to run `gitlab-rails`.
It defaults to `true` because that is what the command normally requires.

Timeouts

gitlab_settings_rails_timeout: 900

Covers the whole staged execution, including the Rails boot. Raise it for very large documents rather than splitting the apply — one boot for a big document still beats several boots for small ones.

Check mode

Rails cannot prove drift without booting Rails, which is a real action on the instance. The role will not pretend otherwise:

Mode --check behaviour
strict The Rails partition fails the run.
compatible Each Rails operation is recorded as an explicit structured skip.

If you need a meaningful dry run across a mixed surface, use compatible and read the coverage manifest: the API operations report real drift, and the Rails operations are listed as skipped rather than silently assumed clean.

Security boundary

A label selector is not authorization

For kubernetes_toolbox, use a dedicated Kubernetes principal limited to pod get/list and pod-exec creation in the GitLab namespace. Isolate that namespace from principals that could create or relabel a pod to impersonate the selected toolbox workload — otherwise the selector can be made to point at something you did not intend.

For ssh_omnibus, the boundary is the SSH principal and its become rights on the Omnibus host. Both strategies run role-owned code, not code assembled from your settings, but they run it with real privilege.