For the complete documentation index, see llms.txt. This page is also available as Markdown.

GitLab

Set up GitLab for Lunar. Create the group access token, configure the Hub, and understand how results are reported and how merge blocking works per tier.

The Hub authenticates with group access tokens, one per top-level group. A token issued on a group also covers every subgroup and project beneath it, so most installations need only one.

This page covers the GitLab side only. The install steps that consume it are in Prerequisites → Step 5 for self-hosted, or the Dedicated setup steps.

Authentication

Create the token

For most installations this is a group access token. Go to your top-level group, then Settings → Access tokens → Add new token. Give it the api scope and the Maintainer or Owner role, and pick an expiry (see Expiry and rotation below).

Lunar takes exactly one token per group, so what that token can do decides which features are available:

Permission
Value
What it enables

scope

api*

Everything Lunar does over the API — reading repositories, commits, and merge requests; posting commit statuses and MR comments

role

Maintainer

Registering project webhooks, and — on Ultimate — provisioning the merge gate: the status check and the Status checks must succeed setting

GitLab has no write scope smaller than api, and Lunar writes commit statuses, MR comments, and webhooks — so read_api is not enough, and the role is what bounds the token. Fine-grained tokens are personal-only; there is no group equivalent.

Copy the token when GitLab shows it — it is displayed once.

Which account the token belongs to

The permissions above are the requirement. Whose token carries them is a separate choice, and it decides whether one optional capability is available:

Option
Token
What you get

Recommended

A service account with permissions on the group — a group access token is the simplest form, since GitLab creates and scopes the bot user for you

Everything in the table above. Nothing is tied to a person, and access stops at the group and its subgroups

Alternative

An instance administrator's personal access token

The same, plus broadcast messages on single-tenant instances

Expiry and rotation

GitLab access tokens carry a fixed expiry, and the Hub reads the token once at startup. Unlike a GitHub App there is no overlap period where both credentials work, so replacing one is a cutover rather than a rollover.

Set a calendar reminder ahead of the expiry date. When a token lapses, Lunar stops posting results and stops reacting to webhooks for that group, and because the Hub only reads the token at startup, nothing fails loudly to tell you.

The mechanics depend on your install: self-hosted is a secret update plus a Hub restart; on Dedicated you re-deposit the token into your install's secret drop.

Hub configuration (Dedicated)

Nothing to configure: Earthly runs the Hub and configures it for you. Continue at Dedicated → Deposit your credentials, where the token goes to your install rather than to Earthly. Everything below is for self-hosted installs; you can skip to Merge gate.

Hub configuration (self-hosted)

Lunar reads GitLab credentials from HUB_GITLAB_TOKENS, a JSON array with one entry per top-level group. Install Step 4 has the values block to paste; this is the field reference behind it.

Entry fields

Field
Required
Meaning

group

Yes

The top-level group path, e.g. acme. Matched by longest prefix, so this token also serves acme/platform/billing.

token_path

Yes

Where the token file is mounted inside the Hub container.

webhook_secret

No

Overrides the chart-generated signing secret (<release>-gitlab-webhook) for this group. Set it when one group should not be able to forge deliveries for another; leave it out otherwise.

host

No

Defaults to gitlab.com. Set it to your instance hostname for self-managed.

base_url

No

Defaults to https://<host>/api/v4. Set it only if your instance serves the API somewhere else.

Multiple groups

Add one entry per top-level group, each with its own token, mounted at distinct paths:

Because matching is by longest prefix, you can also give a subgroup its own entry to override the token its parent would otherwise supply.

Scaling tip. GitLab applies API rate limits per token. A very large group that pushes constantly can approach that limit, since all of Lunar's traffic for the group shares one token. Giving a busy subgroup its own entry gives it its own limit as well, which is the simplest way to spread the load — particularly on gitlab.com, where the limits cannot be raised.

Merge gate

Lunar can block a merge request on every tier. What differs is the mechanism, how precisely it targets Lunar, and who switches it on.

Tier
Mechanism
Blocking

Ultimate

External status check

Lunar provisions and enables it for you

Free, Premium

Commit status

Available, but you enable Pipelines must succeed yourself, and it gates on the whole pipeline rather than on Lunar specifically

Only policies at a gating enforcement level participate. See policies.

External status checks (Ultimate)

On GitLab Ultimate, Lunar blocks a merge request whose gating policies do not pass.

Merge blocking is capability-driven: Lunar detects that a namespace is on Ultimate and switches on automatically. There is no configuration flag, so if you upgrade a namespace's licence later, blocking activates on the next sync without anyone touching Lunar's configuration.

To make blocking work, Lunar changes each project it tracks:

Change
Where you see it in GitLab

Creates a status check named Earthly Lunar

Settings → Merge requests → Status checks

Enables Status checks must succeed

Settings → Merge requests

Subscribes its webhook to comment events

Settings → Webhooks

Lunar re-applies these settings on every sync. Turning them off in the GitLab UI is temporary — they come back. This is deliberate: it keeps the gate from being quietly disabled on one project and drifting out of policy. But it does mean that switching the gate off is not a supported way to stop it; see Turning the gate off.

This requires the group access token to hold the Maintainer or Owner role, since Lunar creates the status check and changes merge-request settings.

Overriding a block

When a merge request is blocked and it needs to merge anyway, comment on it:

A reason is required — lunar bypass: on its own is refused. Lunar replies confirming who bypassed, which commit it applies to, and why. If it cannot honour the request it replies saying so, rather than staying silent.

A bypass covers one commit. Pushing again to the merge request re-blocks it, and needs a fresh bypass. This is the property that makes the override safe to grant: it cannot be used to wave through code that has not been looked at.

Rule
Detail

Who

Maintainer or above on the project

Reason

Required, and recorded

Scope

The merge request's current commit only

Self-bypass

The merge request's own author may bypass; it is allowed and recorded as such

Every bypass is recorded — who, their verified role, the reason, the commit, and whether it was a self-bypass — on the same audit ledger the CLI bypass commands write. That means a comment-driven override shows up in lunar policy bypass-ls, can be ended early with lunar policy bypass-rm, records what it actually masked, and is queryable over the SQL API alongside every other override. Unlike CLI bypasses it carries no expiry: the commit bound is the tighter one, since a new push re-arms the gate.

The comment is not the only way through: a lunar policy bypass-pr from the CLI also clears the check.

Turning the gate off

The gate is fail-closed. If Lunar is unavailable, gated merge requests stay blocked rather than falling open. That is the safe default for a compliance control, but it means you should know the lever before you need it.

To unblock merges during an incident, a project Owner disables Status checks must succeed in Settings → Merge requests.

Commit status (default)

On GitLab Free and Premium there are no external status checks, so Lunar posts a commit status named Earthly Lunar instead. Lunar provisions nothing and changes nothing in your projects on these tiers — it only writes the status.

A commit status folds into the commit's pipeline result, so it can gate merges — but you enable that yourself, per project, in Settings → Merge requests → Merge checks → Pipelines must succeed. With it on, a failing Lunar status makes the pipeline fail and the merge request unmergeable.

Two consequences worth knowing before relying on it:

  • It is not Lunar-specific. The setting gates on the pipeline as a whole, so a Lunar failure and a broken test look the same to it, and you cannot require Lunar without also requiring everything else in the pipeline. The Ultimate status check is a named requirement; this is not.

  • Pending counts as not-succeeded. Lunar reports running while a commit's checks are still evaluating, so a merge request stays unmergeable until Lunar resolves — which is the point, but it means a stuck evaluation blocks merges with no bypass comment available. There is no lunar bypass: on these tiers; the lever is the project setting itself.

PR status reporting

On a merge request, Lunar reports each evaluation as:

  • A named result — the Earthly Lunar status check on Ultimate, or a commit status on Free and Premium. See Merge gate for which of those can block.

  • A merge-request comment summarising the policy results, updated in place as new commits arrive rather than posted repeatedly.

Reporting is driven by merge-request webhooks, so it needs the project webhook Lunar registers — which is why the token's role matters even when you are not gating.

Main branch status reporting

On commits to the default branch, Lunar posts a commit status, on every tier.

Status checks exist only on merge requests, so there is no Ultimate-specific behaviour here and nothing to configure: default-branch reporting looks the same whichever tier you are on. These statuses are informational — there is no merge to block.

Two further surfaces carry default-branch state beyond the commit itself.

Project badges

Lunar maintains a project badge reflecting the latest main-branch result, so a project's standing is visible from its overview page without opening a commit or a dashboard.

Like the merge gate's status check, the badge is provisioned by Lunar using the group access token, so it needs the same Maintainer or Owner role.

Broadcast messages (single-tenant only)

On a single-tenant instance — one your organisation runs, where every user is yours — Lunar can announce results as an instance-wide broadcast message, the banner GitLab shows across the top of every page.

This is deliberately limited to single-tenant instances: a broadcast is visible to everyone on the instance, so it only makes sense when the instance and its users belong to you.

There is nothing to switch on. Lunar uses the one token you configured, and enables broadcasts when that token turns out to have instance-administrator rights — the same capability-driven approach as the merge gate. So the feature follows from which token you supply, not from a setting.

In practice that means broadcasts require a personal access token belonging to an instance administrator, used as the group's token in place of a group access token.

Next steps

Self-hosted installs carry the token into a Kubernetes secret at prereqs Step 6, and into your chart values at install Step 4. On Dedicated you deposit it into your install's secret drop instead.

After that, set up config sync from GitLab CI so the Hub's copy of your configuration stays current as you edit it. Git Platforms has the full capability comparison with GitHub.

Last updated