GitLab behind a VPN or firewall
Use kanman with a self-managed GitLab that is only reachable inside your company network: a runner inside the network does all repository work and talks to kanman over outbound HTTPS only.
If your GitLab is only reachable inside your company network, for example behind a VPN, kanman’s cloud cannot connect to it. You do not need to open your network for kanman. Instead, you run a self-hosted runner inside the network and turn on Run everything on your runners. The runner then does every repository action with its own GitLab token: cloning, branches, merge requests, comments with the evidence pack, CI results. kanman’s cloud keeps the board, decisions, policies and the audit log and never connects to your GitLab.
The same setup works for GitHub Enterprise Server behind a firewall.
How it fits together
- The runner runs inside your network, on a Linux host or VM with Docker, next to your GitLab.
- It connects outbound over HTTPS to kanman, asks for work and reports back. kanman never connects into your network. No inbound ports, no VPN access for kanman, no firewall exception for incoming traffic.
- It reaches your GitLab with its own token (
GITLAB_TOKEN). The token never leaves the runner. - It calls your model provider with the access you configure on it, for example your Amazon Bedrock, Microsoft Foundry or Google Vertex AI account.
What has to be reachable
From the runner host, outbound HTTPS (port 443) to:
| Host | Purpose |
|---|---|
api.kanman.ai |
The runner’s connection to kanman: registration, work, progress, results |
ughtqskbdmitncmopwvj.supabase.co |
kanman’s storage and callback host: evidence uploads and the results of acceptance checks and scans |
Your GitLab, for example gitlab.corp.example |
Clone, push, merge requests, CI (inside your network) |
| Your model provider | For example bedrock-runtime.eu-central-1.amazonaws.com, your Foundry or Vertex AI endpoint, or api.anthropic.com |
ghcr.io |
Only to pull the runner image (or use your own registry mirror) |
Nothing has to reach the runner from outside.
The runner log names the kanman host it uses when it starts (kanmanHost), together with the proxy and certificate settings it found.
Step 1: Create a GitLab token for the runner
Create a token the runner uses for all repository work. A project access token or group access token for the projects of the team is a good fit; a personal access token of a service account works too.
| Setting | Value |
|---|---|
| Role | Developer or higher in every project the team works in (push branches, open merge requests). Maintainer if kanman should merge into protected branches. |
| Scopes | api, read_repository, write_repository |
| Expiry | Match your rotation policy. When it expires, the runner’s checks report “rejected the token”. |
Step 2: Start the runner inside your network
Create a workspace token under Your settings > API tokens (see Run the self-hosted runner). Then start the runner on a host inside the network:
docker run -d --name kanman-runner \
--restart unless-stopped \
-e KANMAN_API_KEY=km_your_runner_token \
-e RUNNER_NAME=corp-runner-1 \
-e GITLAB_TOKEN=glpat-your-runner-token \
-e GITLAB_API_URL=https://gitlab.corp.example/api/v4 \
-e CLAUDE_CODE_USE_BEDROCK=1 \
-e AWS_REGION=eu-central-1 \
ghcr.io/kanman-ai/executor-host
| Variable | Meaning |
|---|---|
KANMAN_API_KEY |
The workspace API token |
GITLAB_TOKEN |
The token from step 1 |
GITLAB_API_URL |
The API address of your GitLab: its web address plus /api/v4, for example https://gitlab.corp.example/api/v4. Use the same host as the repositories you add in step 5. |
| Model access | See step 3 |
RUNNER_NAME |
Optional. Name shown in Workspace settings > Runs and runners |
For acceptance checks the runner also needs Docker with Compose, for example by mounting the host’s Docker socket (-v /var/run/docker.sock:/var/run/docker.sock). See Acceptance checks on your runner.
Pin the image to a version tag in production and update deliberately.
Internal certificates
If your GitLab uses a certificate signed by your company’s own certificate authority, give the runner that CA certificate. Mount the PEM file and set NODE_EXTRA_CA_CERTS:
-v /etc/pki/company-ca.pem:/certs/company-ca.pem:ro \
-e NODE_EXTRA_CA_CERTS=/certs/company-ca.pem \
The runner adds the certificate to everything it starts: its own connections, git (clone, fetch, push) and the coding agent. It builds one bundle from the system certificates and yours and points git at it, so public hosts such as api.kanman.ai stay trusted.
If you already have a complete bundle (system certificates plus your CA), set SSL_CERT_FILE to it instead.
HTTP proxy
If outbound traffic has to go through a proxy, set the usual variables. The runner, git and the coding agent use them:
-e HTTPS_PROXY=http://proxy.corp.example:3128 \
-e NO_PROXY=gitlab.corp.example,.corp.example \
Put your GitLab (and other internal hosts) into NO_PROXY when they are reached directly. Lower-case variable names (https_proxy, no_proxy) work too.
Step 3: Choose the model access
The runner calls the model provider itself. Recommended for companies: your own cloud account, so model traffic stays under your contract and in your region.
- Amazon Bedrock, Microsoft Foundry or Google Vertex AI for Claude Code, Azure OpenAI for Codex: set the provider variables on the runner and choose Model access from the runner environment for the team. See Model access on self-hosted runners.
- Your own key stored in kanman: the runner receives it for each step of that team.
- An Anthropic or OpenAI key on the runner (
ANTHROPIC_API_KEY,OPENAI_API_KEY). - Your company’s AI gateway inside the network: store it under Workspace settings > Model access with Use a gateway or proxy instead (only your runners call it, and Test connection runs on your runner), or set its variables on the runner. See Use your company’s AI gateway and AI gateways in the runner environment. Claude Code needs the gateway’s Anthropic-compatible endpoint, Codex its OpenAI Responses API.
Whichever you choose, it covers every step on the runner: the coding agent as well as drafting stories, the independent reviewer, acceptance specs and kanman’s replies in the conversation. Amazon Bedrock through the runner’s IAM role, for example, needs no extra key for drafting and review.
Different models for coding and for the other steps: to keep Claude for coding and send drafting, review and replies to your company’s AI gateway inside the network, set the KANMAN_ASSISTANT_ variables on the runner. See Different models for coding and for the other steps.
To keep the coding agent from contacting anything besides the model provider, also set CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 on the runner.
Step 4: Run everything on your runners
- Open Workspace settings > Runs and runners.
- Under Where runs execute, choose Everything on ours and confirm.
- Check that your runner shows as Online in the list below.
From now on kanman’s cloud does not call a model provider or a code host for this workspace. See Run everything on your runners for what runs where.
Step 5: Create the team and add the repository by path
kanman’s cloud cannot list the projects of your GitLab, so you add the repository by its path.
- Start the team setup.
- Where do your stories live?: choose the kanman board or Jira (see Tracker choice).
- In Project and repositories, choose GitLab as the code host and fill in Add a repository your runner can reach:
- Host URL: the address you open in your browser, for example
https://gitlab.corp.example - Project path: the full path, for example
platform/backend/api - Default branch: optional. Leave it empty and your runner looks up the project’s default branch.
- Host URL: the address you open in your browser, for example
- Click Add repository.
Your runner checks the repository right away with its own token. The row shows:
| Status | Meaning |
|---|---|
| Checking with your runner… | The runner is checking. This takes a few seconds. |
| Not checked yet | No runner is online. The repository is saved and checked as soon as a runner comes online. |
| Your runner does not check repositories yet | The runner is online but runs an older image. Update it. |
| Your runner reaches it | The runner reached the project, read its default branch and may push and open merge requests. |
You can add more repositories the same way, and later in Team settings > Repositories and tracker, where Check again repeats the check.
Many repositories at once
A team can work on many repositories, for example every project of a GitLab group. Create the team with one of them, then open Team settings > Repositories and tracker and use Add several at once:
- Check the Host URL. It starts with the host of the team’s other repositories.
- Paste the project paths, one per line. Project URLs work too. When kanman should work from a branch other than the project’s default branch, add it after a space:
fm/integrations/flow_object_zendesk develop. - Click Add … repositories.
Your runner checks the first three right away. The others show Not checked yet with Check now, so a long list does not keep your runner busy. Above 25 repositories the list shows a filter.
When a team has several repositories, every story works in exactly one of them:
- Name the repository in your request, for example “Zendesk: map ticket tags”. kanman matches the name (here
flow_object_zendesk) and notes on each draft which repository it will work in. - When your request names none of them, the draft asks Which repository does this story change? Answer with the name and kanman uses it.
- A story that names no repository does not start. Name it in the story or answer the question, then move the story to Ready again.
Maintenance scans cover up to ten repositories of a team per scan day and move on to the next ones the following day, so every repository is scanned in turn. Change it with Repositories per scan under Advanced in Team settings > Maintenance and CI (up to 100). When the same fix is needed in many repositories, kanman makes it in the first one and then rolls it out to the others.
When the check finds a problem
| Message | What to do |
|---|---|
| Your runner has no GITLAB_TOKEN | Restart the runner with GITLAB_TOKEN (step 1 and 2). |
| GitLab rejected your runner’s token | The token is wrong, expired or revoked. Create a new one and restart the runner. |
| Your runner’s token may not open merge requests | Give the token the scopes api, read_repository and write_repository. |
| Project not found | Check the path, and that the token’s user (or the access token) is a member of the project. |
| The token may read, but not push or open merge requests | Give the token at least the Developer role in the project. |
| The branch does not exist | Enter an existing branch, or leave the field empty for the default branch. |
| Your runner cannot reach the host | Check DNS, VPN routing and the proxy settings (HTTPS_PROXY, NO_PROXY) on the runner host. |
| Your runner does not trust the certificate | Mount your CA certificate and set NODE_EXTRA_CA_CERTS (see Internal certificates). |
| The API answers, but git over HTTPS does not | Check the token’s read_repository and write_repository scopes and that git traffic may pass your proxy. |
| GITLAB_API_URL on your runner names another host | Runs work, but acceptance checks and scans clone from the host in GITLAB_API_URL. Set it to your GitLab’s API address. |
When your runner loses the connection to GitLab
If your runner cannot reach your GitLab any more, for example because its VPN dropped, kanman tells your team once in the team chat: “I can’t reach gitlab.example.com from your runner build-1 since 14:02, so code review, mentions and CI checks wait. Is its VPN or network up? I’ll go on by myself as soon as it answers.” Since waiting work carries on by itself, the message is for your information at first. Only if GitLab stays unreachable for more than 30 minutes does it turn into something that needs owners and admins. If several teams use the same runner, the message comes once, not once per team, and if the connection drops again shortly after coming back, kanman updates the same message (“again since 14:30”) instead of writing a new one. The team chat and General also show a notice, gitlab.example.com unreachable from runner build-1 · since 14:02, with a link to your runners.
While GitLab does not answer, kanman does not keep sending your runner work that cannot succeed. It checks every few minutes whether GitLab answers again and then carries on by itself. kanman then adds one line under its message, for example “gitlab.example.com answers again since 14:35; everything that waited goes on.”, and the message no longer needs anyone. Restarting the runner also makes kanman try again right away.
Tracker choice
GitLab issues cannot be the team’s tracker in this mode: the issues live on your GitLab, which kanman’s cloud does not reach. Choose one of these instead:
- The kanman board: stories, columns, comments and history live in kanman. See Without an external tracker.
- Jira (Cloud, or Server and Data Center reachable from kanman’s cloud). See Connect Jira and GitLab.
The code still lives in your GitLab: the runner opens the merge requests there.
What works and what does not
Works, all on your runner:
- Runs: clone, run branch, the coding agent, local verification with your GitLab CI variables, push and the merge request
- Acceptance checks on a fresh checkout (with Docker on the runner)
- The outcome gate’s repository steps: diff, CI pipeline results, commit statuses, the evidence pack as a merge request comment, the state of the merge request (kanman notices merges and closes done in GitLab without a webhook), and merges when someone chooses Merge in kanman or your merge policy lets kanman merge
- The independent reviewer and acceptance specs with your model access
- The acceptance manifest check and the merge request that adds a starter manifest
- Maintenance scans, drafting stories from the conversation, team context from repositories
- Revert merge requests when a kanman merge turned main red or caused an incident
- Code review: kanman reviews merge requests it is requested on or that a team rule covers, and answers review comments on its own merge requests (it checks for new ones every few minutes)
- One default acceptance setup for all repositories of a team without their own manifest
Not available in this mode:
- GitLab issues as the team’s tracker (use the kanman board or Jira)
- Picking repositories from a list in the team setup (add them by path)
- Team context from Confluence, Notion or uploaded files
Related pages
Last updated: January 1, 0001
Open kanman