Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75
5.0 KiB
Git sync with GitLab
GitLab has no equivalent of a GitHub App, so there is nothing to install and no consent screen. What Windmill needs instead is one credential you create in GitLab and paste once. With it, a GitLab repository gets the same managed features an app-backed GitHub repository has: instant pull over a webhook, merge requests opened on deploy, and a diff preview posted onto the merge request.
The credential
Create a group access token on the group that owns the project (Settings → Access tokens), or a group service account and a personal access token for it. Either one is a bot identity that outlives the person who created it, which is what you want for a credential the instance uses unattended.
| Scope | api |
| Role | Developer to push deploy branches; Maintainer to also manage the webhook and open merge requests |
| Expiry | Required for a group access token; a group service account PAT can be non-expiring on self-managed (see below) |
The api scope is what makes the token rotatable, so Windmill can renew it
before it expires. A write_repository-only token can still push, but Windmill
cannot inspect or renew it and reports that in the workspace's git sync settings.
Connecting a repository
In the resource form for a git_repository resource, use the GitLab button:
paste the instance URL and the token, pick a project from the list, and Windmill
stores the whole remote URL, credential included, in a secret variable and
points the resource at it ("url": "$var:u/you/gitlab_group_project_url").
The variable indirection is what makes renewal possible: when Windmill rotates the token it rewrites that one variable, and everything referencing it keeps working. A URL pasted directly into the resource also syncs, but nothing can renew it.
Expiry and renewal
Windmill reads expires_at from the token itself and shows it on the repository
in the workspace's git sync settings. Within three weeks of expiry it rotates the
token through GitLab's own POST /personal_access_tokens/self/rotate, writes the
replacement back to the variable, and verifies it. Only the token can rotate
itself, so a token without api (or self_rotate) is a permanent warning rather
than something Windmill can fix.
Rotation is deliberately never retried. GitLab revokes the old token the instant
it issues the replacement, and presenting an already-rotated token to /rotate
again is treated as reuse: it revokes the whole token family, including the
live replacement. So a rotation that succeeded at GitLab but failed to persist
is surfaced as an error to act on, not retried.
Non-expiring tokens are possible only for a group service account PAT on
self-managed, with require_personal_access_token_expiry turned off in the
instance's application settings. A group access token is always rejected without
an expires_at.
What each managed feature needs
| Feature | Needs |
|---|---|
| Instant pull | A project hook Windmill creates, so Maintainer; and a Windmill base URL GitLab can reach |
| Merge requests on deploy | Developer, plus the api scope |
| Diff preview on a merge request | The project hook, plus permission to post commit statuses and merge request notes |
Instant pull falls back to checking the tracked branch about every minute when the hook cannot be created or delivered, so nothing silently stops syncing.
Self-managed differences
Webhooks to a private network are blocked by default. GitLab refuses to
create a hook pointing at a private or local address until an administrator
enables Allow requests to the local network from webhooks and integrations
(Admin → Settings → Network → Outbound requests,
allow_local_requests_from_web_hooks_and_services). A Windmill instance on the
same private network as GitLab needs this; without it, hook creation fails with a
"blocked" error and the repository keeps polling.
Everything else is identical: Windmill talks to <your-gitlab>/api/v4 and needs
no inbound access of its own beyond the hook deliveries.
Commit statuses create a pipeline
GitLab has no separate check-run concept. Windmill's Windmill diff and
Windmill statuses are commit statuses, and posting one creates an external
pipeline on the project. Two consequences:
- A project with Pipelines must succeed set will not let a merge request merge while a Windmill status is still running, and will block it if the status failed. Windmill therefore always drives a status it created to a terminal state, and reports an informational result (for example "3 changes to deploy") as success rather than leaving it pending.
allow_failureis ignored on the commit-status endpoint, so a Windmill status cannot be made advisory. If you do not want it gating merges, turn the diff preview off rather than expecting it to be non-blocking.
A commit status carries only a name, a 255-character description and a link, so the diff itself goes in a merge request note that Windmill keeps up to date, and the status links to the job.