Skip to content

Work with Git repos

Add a repo to a workbench and Git is ready the moment the container starts: the code is checked out, git push is authenticated, and on GitHub and GitLab the provider CLI is signed in. Everything around the repo - branches, pull requests, CI runs - works from inside the workbench, whether you run the commands yourself or an AI agent does. All it costs is one access token, set up once.

This guide walks that path end to end: create the token, start a workbench with a repo on board, verify the round trip, add and remove repos in a running workbench, and keep a work and a personal account apart on one machine.

The guide talks GitHub throughout, but GitHub is only the stand-in. GitLab works exactly the same way - token, clone, push, and the glab CLI in place of gh. Bitbucket clones and pushes the same way too, and a Bitbucket repo also brings twg, its command-line layer here, from the Atlassian feature - that feature is its own topic.

Repos are cloned into projects/ inside the workbench, one folder per repo (api.git becomes projects/api/). The clone happens inside the container when it starts, authenticated with your access token, and it is idempotent: a folder that already exists is left alone. So your branches, uncommitted changes, and stashes all survive a monoceros apply rebuild. The workspace sits on your disk under ~/.monoceros/container/acme/, and only monoceros remove takes it away - with a backup, by default.

Pushing to a repo - and cloning it in the first place, if it is private - needs you to prove who you are. Over HTTPS that proof is a personal access token, the same kind you would create for a provider’s API. You set it up once, and every workbench you build afterwards can reach your repos.

For GitHub:

  1. Open github.com/settings/tokens.
  2. Click Generate new token, then choose Generate new token (classic).
  3. Give it a note and an expiry, then tick these scopes: repo, read:org, gist, read:user.
  4. Click Generate token and copy the value - GitHub shows it only once.

On GitLab or Bitbucket the moves are the same with different buttons; Git and repositories has the click-by-click steps and scopes for both.

Now store the token where every workbench finds it: monoceros-config.env in your Monoceros home, the global pool all containers read from.

monoceros-config.env
GITHUB_API_TOKEN=ghp_...

That single value does double duty. Monoceros writes it into the container’s git credential store, so git clone, pull, and push authenticate, and into the gh CLI, so it is logged in from the start. A GitLab token goes into the same pool as GITLAB_API_TOKEN; Bitbucket uses an Atlassian token, ATLASSIAN_BITBUCKET_TOKEN (see the Atlassian feature). And if you have more than one account on a provider, several accounts on one machine shows how the pool keeps them apart. The file lives in your Monoceros home, away from any project repo - keep it private.

Create the workbench with the repo on board

Section titled “Create the workbench with the repo on board”

With the token in place, name the repo when you create the workbench:

Terminal window
monoceros init acme --with-languages=node --with-repos=https://github.com/acme/api.git

Nothing is built yet. init writes two files under ~/.monoceros/container-configs/, and they are worth a look (the real files carry explanatory comments, dropped here for brevity):

schemaVersion: 1
name: acme
runtimeVersion: 1.8.0
languages:
- node:22
features:
- ref: ghcr.io/getmonoceros/monoceros-features/github-cli:1
options:
apiToken: ${GITHUB_API_TOKEN}
git:
user:
name: ${GIT_USER_NAME}
email: ${GIT_USER_EMAIL}
repos:
- url: https://github.com/acme/api.git

You asked for Node and one repo, and the yml shows things you did not ask for by name. The github-cli feature came in with the repo, and that is deliberate: a repo in a workbench almost always means more than clone and push - branches, pull requests, CI runs - so Monoceros brings the provider’s CLI along and signs it in with the same token. A GitLab repo brings glab the same way; a Bitbucket repo brings twg, Bitbucket’s command-line layer here, from the Atlassian feature.

The git.user block sets the committer identity for commits made inside the container; fill the two values in acme.env, or set a machine-wide default once in monoceros-config.yml. You can also leave everything blank: apply then uses your host’s global git config, and if that is empty too, it asks you once for name and email and offers to save the answer, so later applies and new workbenches don’t ask again. Git and repositories has the full resolution order. And the empty GITHUB_API_TOKEN= in acme.env is a per-workbench override - leave it empty and the global pool value from the previous step is used.

URLs must be HTTPS. SSH-style URLs (git@github.com:...) are deliberately rejected: they drag host-specific agent setup into a container that is supposed to be reproducible anywhere.

Now build it. apply validates the yml, materializes the container, and brings it up - and its output tells the whole story of this guide. Watch the run, or read its final screen:

Applying: the token is picked, the repo checks out (not a video, so you can select and copy any line)

The first line is the credential pick: your token from the global pool, matched to github.com. While the container starts, the spinner pauses on cloning api… - that is your checkout happening - and the closing summary lists the repo right next to the language and the gh feature. The scaffold also put the repo into acme.code-workspace, so an editor attached to the workbench shows api as its own root in the Explorer.

The container is up with the repo already checked out. See for yourself:

Terminal window
monoceros shell acme

You land in a bash session inside the workbench, with the code at projects/api/. Before you push anything, three quick checks show that the wiring is right. First, ask the GitHub CLI who you are:

Terminal window
$ gh auth status
github.com
✓ Logged in to github.com account jane (GH_TOKEN)
- Active account: true
- Git operations protocol: https
- Token: ghp_************************************
- Token scopes: 'gist', 'read:org', 'read:user', 'repo'

The account is yours, and the scopes are the ones from the token step - the token made it from your env file into the container, and gh is signed in. Second, step into the clone and look at its remote:

Terminal window
$ cd projects/api
$ git remote -v
origin https://github.com/acme/api.git (fetch)
origin https://github.com/acme/api.git (push)

A plain HTTPS URL, with no token embedded in it - the container’s git credential store answers for the host whenever git needs to authenticate. Third, check who your commits will be made as:

Terminal window
$ git config --get-regexp '^user\.'
user.name Jane Doe
user.email jane@example.com

That is the identity apply resolved - from your env values, your defaults, or the one-time prompt. With all three answers in place, the round trip works right away: edit, commit, git push.

There is no login prompt and no credential helper to configure. That is the point of the token step: you did the auth work once, on the host, and every workbench inherits it. And the signed-in gh reaches further than a status check: gh pr create opens a pull request right from this shell, gh run watch follows CI after a push, and on a GitLab repo glab does the same jobs. The login survives rebuilds, so none of this is a first-run-only convenience.

Two weeks in, you need a second repo next to the first. Same idea as every other change, one command against the config:

Terminal window
monoceros add-repo acme https://github.com/acme/web.git

Because the workbench is running, Monoceros does not make you wait for a rebuild: the repo is cloned into projects/web/ right away, using the token from the global pool. The yml is updated either way, so the next apply would produce the same result from scratch.

Two things to know about the live path. First, the clone authenticates now, so the token has to be in place before you run add-repo - on a private repo without a token you get git’s auth error. Second, if the new repo is the first one for its provider, the CLI feature it brings (gh or glab) lands in the yml immediately but arrives in the container on the next apply - features are baked in at build time. The same goes for the folder entry in acme.code-workspace: it is appended on the next apply, and your editor picks the new root up from there.

For a self-hosted host, name the provider so Monoceros knows which token family to use:

Terminal window
monoceros add-repo acme https://git.acme.corp/team/web.git --provider=gitlab

The web experiment is over and you want it out of the config:

Terminal window
monoceros remove-repo acme web

This removes the entry from the yml - and deliberately nothing else. The clone at projects/web/ stays exactly where it is, with every uncommitted change in it; delete the folder yourself when you are sure. The gh or glab feature that came with the repo stays too, since you may well be using it for other repos - drop it with monoceros remove-feature if not. And the folder entry in acme.code-workspace stays as well, since Monoceros never removes workspace folders you may have kept on purpose - take it out in VS Code or Codium with Remove Folder from Workspace, or edit the file.

What it changes is future builds: the next fresh build of this yml, on this machine or a teammate’s, will not contain the repo.

Now the harder case. You have a work account for the acme org and a personal one, both on github.com, and one GITHUB_API_TOKEN cannot serve both. Give each account its own token in the global pool, keyed by name:

monoceros-config.env
GIT_TOKEN__GITHUB_ACME=ghp_...
GIT_TOKEN__GITHUB_JANE=ghp_...

That is the whole setup. Monoceros picks the token per repo from the first path segment of its URL - the GitHub org or user, uppercased. A repo at github.com/acme/api picks GIT_TOKEN__GITHUB_ACME; one under github.com/jane/dotfiles picks GIT_TOKEN__GITHUB_JANE. You configure nothing per workbench, and a workbench that mixes repos from both accounts authenticates each one correctly. Swap the provider in the name and the same scheme covers GitLab groups: GIT_TOKEN__GITLAB_<GROUP>. Bitbucket is the exception - its Atlassian token is user-scoped, so it isn’t keyed this way.

Under the hood it is a cascade, first match wins:

  1. GITHUB_API_TOKEN - the explicit override, per workbench in acme.env or global.
  2. GIT_TOKEN__GITHUB_<SEGMENT> - the account-keyed token above.
  3. GIT_TOKEN__GITHUB - a provider-wide catch-all.

To pin a whole workbench to one account regardless of repo URLs, point its override at the pooled token from acme.env:

acme.env
GITHUB_API_TOKEN=${GIT_TOKEN__GITHUB_ACME}

The same pattern covers the committer side. Commit identity resolves as its own cascade, from a per-repo git.user down to your host git config, so the work repo can commit as your work identity while the personal one keeps your private email. Git and repositories walks the full cascade.