Connecting a forge
nit’s git operations are plain clone, fetch and push. Any remote git can already talk to works: GitHub, GitLab, Gitea, Bitbucket, Azure DevOps, Gerrit, or a bare repository over SSH.
The three things that have to be true
Section titled “The three things that have to be true”Before any of the per-forge detail, this. nit’s guarantees end the moment a developer can push to the same branch directly.
- nit’s machine account is the only writer. Everyone else gets read access at most.
- The branches nit manages are protected, with that account as the only permitted pusher.
- The repository is private. nit hides files inside a repository; it cannot hide a public one.
Without those, a developer who wants a file they may not read simply clones it from the forge, and nit has authorized nothing.
In the bundle
Section titled “In the bundle”- id: backend-api remote: https://github.com/acme/backend-api.git forge: github default_branch: mainforge selects a driver. An unrecognized value falls back to the generic
driver, which:
- injects the token into an
https://remote as HTTP basic auth; - leaves
ssh://and local paths alone, so git resolves the credential from its own configuration.
A specific driver only becomes worth writing for the shortcuts — reading a branch tip without cloning, opening a merge request — and neither is needed for push and pull to work.
GitHub
Section titled “GitHub”Cloud or Enterprise; only the remote host differs.
- id: backend-api remote: https://github.com/acme/backend-api.git forge: github default_branch: mainThe credential. A fine-grained personal access token, a GitHub App
installation token, or a machine user’s classic token with repo scope. Scope it
to exactly the repositories in the bundle.
Locking the repository down:
- Settings → Collaborators: nit’s machine account gets Write; developers get Read at most.
- Settings → Branches → add a rule for the branches nit manages. Enable Restrict who can push and list only the machine account.
- Settings → General: the repository is Private.
docker compose -f compose.base.yaml -f compose.github.yaml up -dGitLab
Section titled “GitLab”- id: backend-api remote: https://gitlab.com/acme/backend-api.git forge: gitlab default_branch: mainThe credential. A project or group access token with write_repository, or a
deploy token. Not a person’s token: it should cover exactly the repositories in
the bundle, and it should not stop working when someone leaves.
Locking the project down:
- The machine account has Maintainer (or Developer with push rights); everyone else has at most Reporter.
- Settings → Repository → Protected branches: only that account may push.
- The project is Private.
docker compose -f compose.base.yaml -f compose.gitlab.yaml up -dGitea can run inside the same stack, which is what the development environment does. Then nit reaches it over the compose network and it never has to be exposed for nit to work:
- id: backend-api remote: http://gitea:3000/acme/backend-api.git forge: gitea default_branch: mainThe credential. An access token with write:repository, from the machine
account’s Settings → Applications → Generate Token.
Locking it down: close registration, give the machine account write on the repository and everyone else read, and protect the branches nit manages.
docker compose -f compose.base.yaml -f compose.gitea.yaml up -dAnything else
Section titled “Anything else”Copy the GitLab overlay, change the comment, set the token. That is the whole adaptation — the generic driver handles the rest.
SSH remotes
Section titled “SSH remotes”- id: backend-api remote: ssh://git@git.example.com/acme/backend-api.git forge: genericLeave forge.token empty and give git its configuration instead:
git: ssh_command: >- ssh -i /run/secrets/nit-ssh-key -o IdentitiesOnly=yes -o UserKnownHostsFile=/etc/nit/ssh/known_hosts -o StrictHostKeyChecking=yes -o BatchMode=yesknown_hosts is not optional. Without it git cannot verify the host, and a
worker that accepts any host key will happily push your repository to whoever
answers.
The worked example walks the whole thing through against GitHub, with a validation dataset.
A local path
Section titled “A local path”Useful for testing. git treats a bare repository on disk exactly as it treats a remote:
- id: backend-api remote: /srv/git/backend-api.git forge: genericVerifying a connection
Section titled “Verifying a connection”nitctl policy validate ./policy # the remote is well-formednitctl tasks -limit 5 # push something and watch it landnitctl audit -limit 5If a push reaches the worker and fails, the task detail carries the reason:
nitctl tasks -state failed -jsonA clone failure deliberately reports only the branch it was cloning, without the underlying git message — because the authenticated remote is a credential and git quotes the URL it was given.
To see more, raise the log level on the worker and look at its own output:
NIT_LOG_LEVEL=debug nit-worker- Going to production — topologies and the security checklist.
- Running workers — sizing and scaling.