Skip to content

Git and repositories

Add a repository URL and your workbench is ready to work. Monoceros clones your repos and signs you in to GitHub, GitLab and Bitbucket, so you can commit and push from inside the container straight away.

All it takes is one personal access token. Set it once and every workbench you build can reach your repositories. The token stays in a private env file.

New to Monoceros? Start with Installation.

Three steps take you from nothing to a cloned, authenticated repo inside the container: create a token, store it, then add the repo. The token is a one-time setup - once it’s in place, adding repos just works, at init or later.

A personal access token is how you prove to GitHub, GitLab or Bitbucket that a clone or push is really you. It’s the same token you’d create to use their APIs or command-line tools, and it stands in for a password over HTTPS. Create one for each provider you work with.

  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 - it starts with ghp_, and GitHub shows it only once.
  1. Open Personal access tokens in your user settings.
  2. Click Generate token, then choose the Legacy token option.
  3. Give it a name and expiry, then tick these scopes: api, read_repository and write_repository (drop write_repository if you only ever clone).
  4. Click Create personal access token and copy the value - it starts with glpat-, and GitLab shows it only once.

Bitbucket uses an Atlassian API token with scopes. Fastest: this pre-filled link opens the token form with Bitbucket as the app and every scope selected - create it as is and copy the value (Atlassian shows it only once), or step Back to the start to rename or pick fewer scopes.

Which scopes matter: cloning and pushing needs read:repository:bitbucket and write:repository:bitbucket (drop the write scope if you only clone); twg’s pull-request, pipeline and workspace commands add read:pullrequest:bitbucket and write:pullrequest:bitbucket, read:pipeline:bitbucket and write:pipeline:bitbucket, and read:workspace:bitbucket.

Or step through it by hand:

  1. Open Atlassian API tokens.
  2. Click Create API token with scopes.
  3. Give it a name and expiry, then continue.
  4. Choose Bitbucket as the app, then continue.
  5. Tick the scopes from the list above.
  6. Create the token and copy the value - Atlassian shows it only once.

Save it in an env file beside your configs, under the variable for its provider. The simplest place is monoceros-config.env in your Monoceros home - a global pool every container reads, so you set a token once and it works everywhere:

monoceros-config.env
GITHUB_API_TOKEN=ghp_...
GITLAB_API_TOKEN=glpat-...
ATLASSIAN_BITBUCKET_TOKEN=...

This file holds your secrets, so keep it private - it lives in your Monoceros home, away from any project repo. For a token that should apply to one container only, put it in that container’s <name>.env (e.g. acme.env, which is gitignored) instead; a value there wins over the global pool.

The one token does double duty: Monoceros writes it into the container’s git credential store (so git clone / push works) and into the provider CLI feature (so gh / glab is logged in).

It also covers a private plugin marketplace. An agent plugin is cloned inside the container by the same git, so a marketplace on a private repository needs the same token as a private repo, from the same env files. Monoceros says so before it builds anything if the token is missing. See Claude Code.

The yml never holds a secret itself. Wherever it needs a value from the env - a git identity, a feature’s own token - it carries a ${VAR} placeholder that Monoceros fills at apply time from these env files (<name>.env first, then the global pool). So a name like ${GIT_USER_NAME} in the yml is just a lookup into your env; Configuration covers the yml/env split in full.

With the token in place, point Monoceros at your repositories.

At init - pass one or more repos (comma-separated) when you create the container:

Terminal window
monoceros init acme \
--with-repos=https://github.com/acme/api.git,https://github.com/acme/web.git

They’re recorded in the yml and cloned into projects/ on the first apply, one folder per repo (derived from the URL: api.gitprojects/api/). monoceros init accepts canonical hosts here (github.com / gitlab.com / bitbucket.org); for a self-hosted host or a custom target folder, add the repo afterwards (below) and set path / provider in the yml.

Later, one at a time - monoceros add-repo adds a single repo to an existing config. The example below adds two: a GitHub repo, and a self-hosted GitLab repo whose host Monoceros can’t auto-detect, so it names the provider explicitly.

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

If the container is running, the repo is cloned into it right away, using the token from step 2 - so if that token isn’t set yet, the clone fails with an auth error. On a running container, create and store the token first, then add-repo.

Either way, the repos end up in the yml under repos:

acme.yml
repos:
- url: https://github.com/acme/api.git
- url: https://git.acme.corp/team/web.git
provider: gitlab

github.com, gitlab.com and bitbucket.org are detected automatically; for any other host, add provider (github / gitlab / bitbucket) so Monoceros knows which token to use. Each repo clones into a folder under projects/ named after the repo - add path to an entry to choose a different folder. URLs must be HTTPS, and cloning is idempotent - an existing projects/<path>/ is left untouched, so your local changes and branches survive a monoceros apply rebuild. On apply, Monoceros reports which token it used for each repo:

Using GITHUB_API_TOKEN for github.com

Not every workbench starts from an existing repo. Often you’re building something new: you scaffold a project inside the container, and then you want to create the remote, push it, and open pull requests without ever leaving it. Add the provider CLI on its own - no repo required:

Terminal window
monoceros init acme --with-features=github
monoceros init acme --with-features=gitlab

The first gives you gh, the second glab; add --with-features=github,gitlab for both. With the token from step 2 in place, they’re already logged in, so from inside the container you go straight from a fresh project to a pushed repo. With gh:

Terminal window
gh repo create acme/new-app --private --source=. --push
gh pr create

Or with glab:

Terminal window
glab repo create new-app
glab mr create

It’s the same token from step 2 doing its job, no repo needed. Once you apply, gh and glab are authenticated inside the container, so creating the remote and pushing your first commit is a one-liner away.

Working across a work org and a personal account? Give each its own token in the global pool, named GIT_TOKEN__<PROVIDER>_<ACCOUNT> - here a work org (ACME) and a personal account (JANE):

GIT_TOKEN__GITHUB_ACME=ghp_...
GIT_TOKEN__GITHUB_JANE=ghp_...
GIT_TOKEN__GITLAB_ACME=glpat-...

Monoceros keys each repo to the right token by the first path segment of its URL - the GitHub owner or organization, or the GitLab group (uppercased, non-alphanumerics turned to _). So github.com/acme/api picks GIT_TOKEN__GITHUB_ACME and a repo under your personal namespace picks GIT_TOKEN__GITHUB_JANE, straight from the URL - no per-container setup.

To pin one container to a specific account regardless of the repo URL, reference the pooled token from its <name>.env (the acme.env tab above) - it doesn’t copy the secret, just points at it.

Under the hood that’s a cascade; for each repo apply takes the first var that’s set:

  1. <PROVIDER>_API_TOKEN - the explicit override (also settable per container in <name>.env).
  2. GIT_TOKEN__<PROVIDER>_<SEGMENT> - the account-keyed token above.
  3. GIT_TOKEN__<PROVIDER> - a provider-wide catch-all.

This cascade is for GitHub and GitLab. Bitbucket authenticates from the single ATLASSIAN_BITBUCKET_TOKEN (its token is user-scoped, so it isn’t keyed per workspace) - see the Atlassian feature.

For a host Monoceros doesn’t recognise, declare the provider and key a token to its group segment (or use the provider-wide GIT_TOKEN__GITLAB):

acme.yml
repos:
- url: https://git.acme.corp/team/web.git
provider: gitlab
monoceros-config.env
GIT_TOKEN__GITLAB_TEAM=glpat-...

Commits made inside the container need a name and email. Monoceros resolves them as a cascade, most specific first:

  1. Per-repo - repos[].git.user overrides the identity for that one repo. Useful when one workbench holds a work repo and a personal repo that need different committers.
  2. Container-level - the top-level git.user applies to every repo in the workbench. Use ${VAR} references so the values come from acme.env, not the shareable yml.
  3. Your env - GIT_USER_NAME and GIT_USER_EMAIL in monoceros-config.env, or in a container’s own acme.env. No yml entry is needed for these to apply.
  4. Machine-wide default - defaults.git.user in monoceros-config.yml applies to every container you build, and takes precedence over your host git config.
  5. Host fallback - if none of those is set, apply reads your host’s git config --global user.name / user.email.
  6. One-time prompt - if even that is empty, apply asks once and writes the answer to the env file you pick, so later applies and new containers don’t prompt again.

Set it once in the env, and every container you build from then on has it:

monoceros-config.env
GIT_USER_NAME=Jane Doe
GIT_USER_EMAIL=jane@example.com

That file is the right home for it: a name and an address are personal data, the env is gitignored, and the yml is the part you can share. monoceros check tells you when a container has no identity, because the failure otherwise shows up as “Please tell me who you are” on your first commit inside it.

Every step in that list is a file you can edit, and that is the whole point: remove the identity from where you put it, apply, and the container no longer has one. The .monoceros/gitconfig inside the container is written by apply and never read back as a source. If it were, an identity would outlive the thing that declared it.

A container - or a single repo within it - can still override that. The yml reads its committer from ${GIT_USER_NAME} / ${GIT_USER_EMAIL} in acme.env (init seeds those keys for you to fill in):

git:
user:
name: ${GIT_USER_NAME}
email: ${GIT_USER_EMAIL}
repos:
- url: https://github.com/acme/api.git
git:
user:
name: Jane Work
email: jane@acme.example

So this container commits as Jane Doe by default, while the api repo overrides that to commit as Jane Work.