Managing Repositories, Organizations, and Enterprises

May 17, 2026 ยท View on GitHub

GARM manages runners at three levels: repositories, organizations, and enterprises. Each entity is associated with credentials that determine the GitHub/Gitea endpoint it belongs to.

Endpoints

Endpoints tell GARM where to find the GitHub or Gitea API. A default github.com endpoint is created automatically.

List endpoints

garm-cli github endpoint list

Add a GHES endpoint

garm-cli github endpoint create \
  --name my-ghes \
  --description "Internal GHES" \
  --base-url https://ghes.example.com \
  --upload-url https://upload.ghes.example.com \
  --api-base-url https://api.ghes.example.com \
  --ca-cert-path /path/to/ca-cert.pem

The --ca-cert-path is optional. Use it when your GHES uses certificates signed by an internal CA.

Add a Gitea endpoint

garm-cli gitea endpoint create \
  --name my-gitea \
  --description "Internal Gitea" \
  --api-base-url https://gitea.example.com/ \
  --base-url https://gitea.example.com/ \
  --ca-cert-path /path/to/ca-cert.pem

The --ca-cert-path is optional. Use it when your Gitea server uses certificates signed by an internal CA.

Delete an endpoint

garm-cli github endpoint delete my-ghes

Important

You cannot delete an endpoint that has credentials associated with it or is in use by entities. The default github.com endpoint can be deleted if nothing references it.

Repositories

Add a repository

garm-cli repo add \
  --owner your-org \
  --name your-repo \
  --credentials my-pat \
  --random-webhook-secret \
  --install-webhook \
  --pool-balancer-type roundrobin
OptionDescription
--ownerGitHub user or organization that owns the repo
--nameRepository name
--credentialsName of credentials to use (must exist)
--random-webhook-secretGenerate a secure random webhook secret
--install-webhookAutomatically install the webhook in GitHub
--pool-balancer-typeroundrobin (default) or pack

The --pool-balancer-type controls how GARM picks a pool when multiple pools match a job:

  • roundrobin -- cycles through matching pools
  • pack -- fills one pool before moving to the next (ordered by priority)

Important

Pool balancing only applies to pools. For scale sets, GitHub handles scheduling.

List repositories

garm-cli repo list

Delete a repository

garm-cli repo delete <REPO_ID>

This removes the entity from GARM and cleans up the webhook if one was installed via the Controller Webhook URL. GARM only removes webhooks that are namespaced to its own Controller ID -- webhooks pointing to the base Webhook URL are left untouched.

Important

The credentials used when adding an entity determine which endpoint (and therefore which forge) it belongs to. If you later swap credentials on an entity, the new credentials must belong to the same endpoint as the original ones.

Organizations

Add an organization

garm-cli org add \
  --name my-org \
  --credentials my-pat \
  --random-webhook-secret \
  --install-webhook

List organizations

garm-cli org list

Delete an organization

garm-cli org delete <ORG_ID>

Enterprises

Enterprise webhook management is manual -- GARM does not install or manage webhooks at the enterprise level. The level of API access required for enterprise webhook management is broad, and since most organizations have only one enterprise, the effort-to-risk ratio doesn't justify automating it.

Add an enterprise

garm-cli enterprise add \
  --name my-enterprise-slug \
  --credentials my-enterprise-pat \
  --webhook-secret "your-secure-webhook-secret"

The --name is the enterprise "slug".

After adding, manually configure the webhook in GitHub Enterprise using the Controller Webhook URL from garm-cli controller show.

List enterprises

garm-cli enterprise list

Delete an enterprise

garm-cli enterprise delete <ENTERPRISE_ID>

Managing webhooks

GARM can manage webhooks for repositories and organizations (not enterprises).

Show webhook status

garm-cli repo webhook show <REPO_ID>
garm-cli org webhook show <ORG_ID>

Install a webhook

garm-cli repo webhook install <REPO_ID>
garm-cli org webhook install <ORG_ID>

Uninstall a webhook

garm-cli repo webhook uninstall <REPO_ID>
garm-cli org webhook uninstall <ORG_ID>

Manual webhook setup

If you prefer to set up webhooks manually in the GitHub UI, see Webhooks.

Controller settings

Controller settings define the URLs that runners and GitHub/Gitea use to communicate with GARM, along with operational parameters like job backoff and agent tools sync. These settings are critical -- if they're wrong, runners can't phone home and webhooks won't arrive.

Viewing controller settings

garm-cli controller show

Will output something similar to:

+---------------------------+----------------------------------------------------------------------------+
| FIELD                     | VALUE                                                                      |
+---------------------------+----------------------------------------------------------------------------+
| Controller ID             | a4dd5f41-8e1e-42a7-af53-c0ba5ff6b0b3                                       |
| Hostname                  | garm                                                                       |
| Metadata URL              | https://garm.example.com/api/v1/metadata                                   |
| Callback URL              | https://garm.example.com/api/v1/callbacks                                  |
| Webhook Base URL          | https://garm.example.com/webhooks                                          |
| Controller Webhook URL    | https://garm.example.com/webhooks/a4dd5f41-8e1e-42a7-af53-c0ba5ff6b0b3     |
| Agent URL                 | https://garm.example.com/agent                                             |
| Minimum Job Age Backoff   | 30                                                                         |
| Version                   | v0.2.0                                                                     |
+---------------------------+----------------------------------------------------------------------------+

Controller URLs

GARM exposes several URLs that serve different purposes. Each URL must be reachable by a specific party (runners or GitHub/Gitea). GARM cannot auto-detect these URLs because it may sit behind a NAT, load balancer, or reverse proxy where the externally reachable address differs from the local one.

When you initialize GARM for the first time, it assumes that all URLs share the same base address you logged in with. This works for most setups. If your network topology is more complex (e.g. separate internal and external addresses, or a reverse proxy with different paths), you'll need to update them.

URLWho must reach itPurpose
Metadata URLRunnersRunners connect here during bootstrap to retrieve setup information (runner token, labels, etc). Injected into runner userdata.
Callback URLRunnersRunners connect here to send status updates and system info (OS version, runner agent ID, etc) back to the controller. Injected into runner userdata. Authentication uses a short-lived JWT scoped to the specific instance -- it can only update that instance's status and fetch its own metadata. Token validity equals the pool bootstrap timeout (default 20 min) plus the GARM polling interval (5 min).
Agent URLRunners (agent mode)In agent mode, runners connect here via WebSocket for bidirectional communication instead of using callbacks. Only needed when agent mode is enabled on an entity.
Webhook Base URLGitHub / GiteaThe base path where GitHub/Gitea sends workflow_job webhook events. Prefer using the Controller Webhook URL instead.
Controller Webhook URLGitHub / GiteaSame as the Webhook Base URL but with the Controller ID appended (e.g. /webhooks/<controller-id>). This is the preferred webhook URL because it's unique per GARM installation, allowing multiple controllers on the same repo/org. Automatically derived from the Webhook Base URL.

The default URL paths match GARM's internal routes:

SettingDefault path
Metadata URL/api/v1/metadata
Callback URL/api/v1/callbacks
Agent URL/agent
Webhook Base URL/webhooks

Other controller settings

SettingDefaultDescription
Controller ID(auto-generated)Unique identifier for this GARM installation. Runners are tagged with this ID so multiple controllers can manage the same repos without conflicts.
Minimum Job Age Backoff30Seconds to wait after receiving a job webhook before spinning up a runner. Gives existing idle runners time to pick up the job. Set to 0 for immediate reaction (useful for scale-to-zero).
CA Cert Bundle(none)Optional CA certificate bundle injected into runner userdata. Allows runners to trust certificates signed by an internal CA when communicating with the controller.
GARM Agent Tools Sync URL(none)URL for automatic garm-agent binary sync, typically the garm-agent GitHub releases API (e.g. https://api.github.com/repos/cloudbase/garm-agent/releases).
Tools Sync EnabledfalseWhether GARM periodically fetches new garm-agent releases from the sync URL.

Updating controller settings

garm-cli controller update \
  --metadata-url https://garm.example.com/api/v1/metadata \
  --callback-url https://garm.example.com/api/v1/callbacks \
  --webhook-url https://garm.example.com/webhooks \
  --agent-url https://garm.example.com/agent

Other settings:

# Change job age backoff
garm-cli controller update --minimum-job-age-backoff 15

# Set a CA certificate bundle
garm-cli controller update --ca-bundle /path/to/ca-bundle.pem

# Clear the CA certificate bundle
garm-cli controller update --clear-ca-bundle

# Enable automatic agent tools sync
garm-cli controller update \
  --garm-tools-url https://api.github.com/repos/cloudbase/garm-agent/releases \
  --enable-tools-sync

After updating URLs, verify they are routable to the correct GARM API endpoints and accessible by the relevant parties (runners for metadata/callback/agent, GitHub/Gitea for webhooks).