# 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 `/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_failure` is 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.