Skip to content

Git Tokens (Dev & Non-Dev App Servers)

New here? Git workflows in Muppy explains the model in one page — why it works this way, and which tokens to create.

Muppy clones your application's git repository onto every App Server it provisions, and re-clones/pulls on each (re)deploy. This guide explains how the git credentials work, and what token to create depending on the kind of server.


TL;DR — which tokens do I actually need?

One token gets you working. The second is an optimization you earn.

Token What it costs you What it buys
1 Read-write, account-wide PAT — one per git host created once, covers every repository forever Everything on a Dev box: clone, pull and push, each released by an approval you give at the moment of the operation. This alone is a complete, supported setup.
2 Read-only, repo-scoped token — a Deploy Token or a fine-grained PAT, one per repository real work, repeated for each repo Required on a non-dev box: it is the credential embedded on disk. On a Dev box it is a bonus — reads stop asking anything at all.

This ordering is deliberate. Creating a repo-scoped token is per-repository work at the provider, and you should not have to do it before you know whether you will keep the repository for more than an hour. So Muppy never asks for token #2 as the price of entry — start with #1, add #2 for a repository once it has earned it.

What that looks like on a Dev box

You have Clone / pull Push
Token #1 only each read asks (one approval covers a window) each push asks
#1 + #2 for this repo nothing asks — git just works each push asks

The upgrade is invisible: nothing to reconfigure on the box, nothing to redeploy. Store token #2 in Muppy and the next git pull stops asking.

Why a repo-scoped read-only token is served without asking

The approval exists to contain what a credential could do if the box were compromised. A token that is both read-only and limited to one repository is already contained — it cannot push, and it cannot reach anything else — so serving it freely costs nothing. An account-wide token, even read-only, can reach every repository you can, so it is only ever released by an approval.

This is why repo-scoped matters, not just read-only: a read-only wide token is token #1 with fewer rights, not token #2.

Every case, in one table

The two rows above are the common setup. There are four kinds of token in all, and what each one gets you is worth having in one place — the answer to "I have this token, what happens when I pull?" should not have to be assembled from three pages.

The token Muppy serves Clone / pull Push The window it opens
Read-only, one repository nothing asks never — it cannot write
Read-only, account-wide one approval never — it cannot write 8 h
Read-write, one repository one approval one approval 15 min, sliding
Read-write, account-wide one approval one approval 15 min, fixed

The window follows the token, not the operation. A git pull served by a read-write token opens the 15-minute window, not the 8-hour one — because what the window exposes is everything that token can do, and this one can push. Only a token that cannot write earns the long window.

Sliding means the window restarts on each of your own pushes, so it follows the work and closes shortly after it stops. A read never extends it: a read cannot be told apart from an editor's background fetch, which would hold the window open all day.

Two of these lines are your administrator's to move — and one is not

Free reads and sliding are settings. Turned off, a repo-scoped token is simply treated like its account-wide equivalent: row 1 becomes row 2, row 3 becomes row 4. Turning a setting off never invents a new behaviour, it only withdraws a privilege — so the table stays readable whatever your estate has chosen.

The exception is the last line. A token that can push anywhere never gets a sliding window, whatever anyone sets. That one is a guarantee, not a policy.


The two server models

The credential model is picked automatically by the qualifier of the server:

Server kind Credential model Token on the box? Can push?
Dev (Development) Muppy git credential helper No — resolved per operation Yes, via an explicit approval at the moment of the push
Non-dev (Test / Staging / Prod) Embedded deploy token Yes — read-only token on disk No (read-only)

The guiding principle: a push (write) can poison the repository and propagate to CI/prod, so write access is tightly controlled; a read is low value (the code is already checked out on the box).

That is also why token #2 is optional on Dev but mandatory on non-dev: a Dev box resolves a credential per operation and can therefore ask a human, while a non-dev box holds its credential on disk with nobody present to approve anything — there is nobody to ask, so the credential itself must be the containment.


1. The building block: a Muppy git token (vault)

A git credential is stored in Muppy as a Vault record (Infrastructure → Vaults, type Git PAT):

Field Meaning
Server the git host, e.g. gitlab.com
User the username git authenticates as
Password the token value (PAT or deploy token)
Access level Read-only or Read-write — advisory in Muppy; the git provider enforces the real scope
Default for this server when set, the token is auto-resolved for any repo on that host (host-wide)

Let Introspect token set Access level and Token scope

Both fields start as your declaration of what the token is, and Muppy honours them when serving credentials — so getting them wrong has consequences (a token declared read-only but scoped account-wide would be handed out for reads with nothing asked).

On GitLab and Forgejo / Codeberg you do not have to get them right by hand: click Introspect token and Muppy asks the provider what the token really is, then corrects both fields in place. See Managing GitLab Tokens for Muppy. The provider remains the real enforcement either way.

A checked claim and a typed one look identical on the form

That correction is not available everywhere. On Azure DevOps Muppy cannot ask at all, and on GitHub it can only ask about a classic token — the reach of a fine-grained one is never reported. Where nothing corrects you, whatever you typed is what Muppy believes, permanently.

It matters at exactly one place: the free-read path. Read-only and Repo-scoped together buy a token the right to be served with no session and no prompt. Where those two fields were verified, that is a guarantee. Where they were typed, it is a good intention — and a token that is actually account-wide, or actually able to write, is being handed out for reads with nobody asked.

So on a provider Muppy cannot check, treat those two fields as something to get right the first time, and prefer a token whose narrowness you can see at the provider.


2. How the Create App Server wizard fills the token (auto-resolution)

When you pick an Application Definition and a qualifier, the wizard tries to auto-fill the Git Access Token. What it proposes — or whether it leaves the field empty — follows a few simple rules. This is the most common source of confusion ("the wizard brings back nothing").

The rule: tokens are resolved per owner, by host

Auto-resolution looks for a vault Git PAT matching, in order:

  1. a token owned by the server's Owner (the Owner field — defaults to you), on the repository's host (e.g. gitlab.com), marked Default for this server;
  2. failing that, a system token — a vault with no owner — on the same host.

Key points:

  • Matching is on host + provider + owner, not on the repository path. So gitlab.com/teamA/repo and gitlab.com/teamB/repo resolve to the same token for a given owner.
  • If neither a token of yours nor a system token exists for that host, the field stays empty — this is expected, not an error. Pick/enter one manually, or create one (see §5).

« The wizard brings back nothing »

The most frequent cause: the only tokens for that host belong to another user. A vault Git PAT has an owner, and auto-resolution only sees your own tokens (plus owner-less system tokens). If you are logged in as a different user than the token's owner, nothing auto-fills — by design. Fix it by creating your own token, or by declaring a system token (leave Owner empty) as a shared fallback for everyone on that host.

What changes per server kind

  • Dev → the box uses the helper (§3), so the wizard embeds no token at all: the field is hidden, replaced by an info banner showing the read-only token the helper will use. An empty/hidden token field is normal for dev.

    Empty qualifier ⇒ treated as dev

    The wizard has no default qualifier; until you pick one it assumes dev. So right after selecting an App Definition the token field is hidden (helper mode). Pick a non-dev qualifier to switch to an embedded token.

  • Non-dev (Test / Staging / Prod) → the wizard pre-fills the App Definition's per-category deploy token (the Test/Staging/Prod default repository token) when set; otherwise it falls back to the per-owner host resolution above. The field is shown and you can override it.


3. Dev servers — the credential helper (no token on the box)

A Dev App Server never stores a token on disk. Instead, the git-credential-muppy helper is installed; on each git operation it calls Muppy, which resolves the token on demand and hands it to git. Nothing is written to disk.

What you create (once, as the server owner)

One read-write PAT for the host is enough to start. Store it as a vault with Access level = Read-write and mark it Default for this server (host-wide) so the helper resolves it. That is the whole setup — see the TL;DR.

Later, per repository, you may add a read-only, repo-scoped token (Deploy Token or fine-grained PAT) as a vault with Access level = Read-only, also marked Default for this server. Reads on that repository then stop asking anything.

Both defaults on the same host is correct

Muppy resolves the default token per access level — read-only for a read, read-write for a push. So having one read-only default and one read-write default on gitlab.com is the intended setup, not a conflict. Only two defaults at the same access level are ambiguous, and Muppy will say so.

The operation then selects which one is served: a read takes your read-only PAT if you have one, a push takes your read-write PAT.

A read may be served your read-write token

If you have no read-only token, a read is served your read-write one — that is what makes the one-token setup work. It is not a loophole: without that fallback your only way to clone would be to authorize a push, which is a broader grant, not a narrower one.

It does come at a price you should know about: that authorization takes the short window, because the window is set by what the token can do, not by what you asked for. Creating a read-only token is what makes reads comfortable again.

An organization that has provisioned read-only tokens for everyone can turn the fallback off and get a hard guarantee that a read never carries write capability — see Read-only token policy.

Reading vs pushing on a Dev box

  • Read (clone/pull) depends on which tokens you hold:
    • with a read-only, repo-scoped token for this repository → nothing is asked, git just works;
    • otherwise → your first read asks you to authorize it, and the approval covers a window.
  • Push requires an explicit, human-approved authorization — always, whatever tokens you hold. On the box:
muppy-git-login <your-muppy-username>          # names your identity (approve it in Muppy)
muppy-git-login <your-muppy-username> --force  # claim a box someone else is holding
git push                                       # BLOCKS and asks you to authorize it
muppy-git-logout                               # close the session
muppy-git-status                               # what this box can currently do

The login grants nothing — the push asks

muppy-git-login establishes who this box acts as. It releases no credential. Approving it asks you to type the short code the command printed — see why. Your git push then pauses, prints an approval link, and waits: you choose Allow once (this operation only) or Allow for N minutes (every operation on this repository for a while).

That pause is the point. The authorization is created because your command is holding the line for it and is consumed by that same command — so there is never an approved credential sitting on a box waiting for somebody to spend it.

A session has no expiry. It ends when you run muppy-git-logout, when another user claims the box, or at the nightly cleaning hour.

When this box has no session at all, git push does not wait — it prints how to open one (muppy-git-login). No token ever lands on disk.

Provisioning the box

At creation, Muppy auto-approves a short read grant so the first clone — and the build, which fetches dependency add-ons — can authenticate. The owner therefore needs a token for the host: the read-only one if it exists, otherwise the read-write one.

Session lifetimes & login timeouts

How long an approval window lasts, how long you have to approve a login, and how long muppy-git-login waits before giving up are all configurable — see Git Credential Settings. In particular: a login that times out before you approve leaves nothing behind — re-run muppy-git-login; the next git command does not pick up the approval on its own.


4. Non-dev servers — an embedded read-only deploy token

A Test / Staging / Prod App Server is usually unattended (it pulls to deploy with no human present), so it uses a deploy token embedded in the remote URL. The deploy token is read-only so a compromised box cannot push.

What you create

  1. In GitLab, create a Deploy Token (not a personal token): Project (or Group) → Settings → Repository → Deploy tokens → scope read_repository. GitLab gives you a username (e.g. gitlab+deploy-token-42, or a custom one) and a token value.

    Project vs Group deploy token

    A build pulls the app repo plus its submodules and ikb dependency add-ons — often several private repos. A project deploy token covers one repo; if the dependencies live in the same group, create a group deploy token so it covers them all.

  2. Store it as a Muppy vault (type Git PAT):

    • Server = gitlab.com
    • User = the deploy token username (e.g. gitlab+deploy-token-42) — not your login; an empty user falls back to oauth2 and GitLab refuses it.
    • Password = the deploy token value
    • Access level = Read-only
  3. Assign it on the Application Definition, per category: the Test / Staging / Prod default repository token field. Servers duplicated from that App Definition embed it automatically.

Why read-only

The token is written to the box's git remote URL. A read-write token there could push and poison the repo if the box is compromised. The create-server wizard shows a red warning when the selected deploy token is read-write — use a read-only one.

Rotation

Deploy tokens can expire. When they do, update the vault's Password (and expiry) with a freshly minted deploy token; re-provision (or re-deploy) picks it up.


5. Creating the tokens on GitLab — quick reference

Start here — Personal Access Token, read-write (token #1, one per host, once): Profile → Preferences → Access Tokens → scopes read_repository + write_repository. This is all you need to work on a Dev box.

Then, per repository if it earns it — a read-only, repo-scoped token (token #2), either:

  • a Deploy TokenProject/Group → Settings → Repository → Deploy tokens → scope read_repository; or
  • a fine-grained PAT restricted to that project — permission Code: Download.

    Code: Read is not enough

    Code: Download is the permission git clone / git pull actually require over HTTPS. A token with only Code: Read authenticates and then fails the clone with a 403. This is the single most common mistake — the full walkthrough is in Managing GitLab Tokens.

For the full step-by-step (GitLab's current fine-grained PAT UI, exactly which permission checkboxes to tick, and the 403 "credential valid but has no access" troubleshooting), see Managing GitLab Tokens.

Personal Access Token Deploy Token
Tied to a user a project / group
Used for Dev (helper, owner identity) Non-dev (embedded deploy), or Dev free reads
Muppy Access level rw (token #1), ro if you also scope one ro only
Muppy User your git username the deploy-token username

6. Re-checking your tokens

A token's status in Muppy — active, expired, revoked — is a measurement, not a declaration. It says what the provider answered the last time somebody asked. Revocation happens at GitLab, and GitLab does not call Muppy about it.

So there is a button that asks about all of them at once. Git > My Git Connections, open your connection, Introspect all my tokens. One task per token, so a slow answer for one holds up no other, and each credential's Token Status and checked age update as its answer comes back.

Why isn't this automatic, every night?

Because it could not work, and the reason is worth knowing.

A healthy classic token can describe itself — Muppy asks it directly and needs nothing else. A token that cannot — a fine-grained one, or a dead one, which is exactly the case you want caught — has to be read through your provider connection. Those connections are short-lived on purpose: they expire when you stop using them, so that Muppy is not holding standing access to your provider account.

A nightly job would therefore have worked for the tokens whose answer never changes, and failed for every token whose status had actually changed. The button runs at a moment when the connection is there — because you are.

Two things worth knowing:

  • Revoked and expired are final. Muppy skips them — the answer will not change, and the call would be spent confirming it. If every token is already dead, the button says so rather than doing nothing.
  • A token Muppy cannot recognise is reported once, not retried. GitLab never reveals a token's value after you create it, so Muppy matches a credential to your tokens by provider id, else by name. If neither matches, it records why and moves on — use Link Git Token on the credential to pick the right one, which sets the reliable link for good.

If your connection has lapsed, the button says so and invites you to reconnect. It queues nothing in that case: forty failed tasks would bury the one fact you needed.

7. Roadmap

  • Per-push write approval — every repository alteration gated by a human permission toast (the highest-security write model).
  • OAuth minting — instead of stored tokens, Muppy mints short-lived, repo-scoped tokens on demand (read for non-dev deploys, one-shot write per approved push), so no long-lived token is stored anywhere.

See the gitlab_integration spec for the design.