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:
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:
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
Pick the administrator token only if you want broadcasts. It hands Lunar instance-wide admin rights for everything it does, not just the broadcast call, and it is tied to a human's account — so it dies with that account and rotates on their schedule.
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
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.
A self-managed host must be listed here. Lunar decides whether a host is GitLab from this configuration. If you omit host for a self-managed instance, Lunar treats that host as GitHub, and every operation against it fails in confusing ways. gitlab.com is recognized without configuration.
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.
Free, Premium
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.
There is no setting to turn this on, and Lunar changes your projects to enable it. Read what it changes below before rolling Lunar out to an Ultimate namespace.
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:
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.
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
runningwhile 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 nolunar 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 Lunarstatus 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
