Manage Runners Logo
Manage Runners
Documentation menu

Manage runners with the CLI

List, create, update, pause, resume, duplicate and delete runners, wait for them, and read their metrics from the terminal.

The examples use the active profile and organization. Add --profile or --org to choose others, and --output json for scripts. Commands that change runners need the scopes from Authentication. runner update and --wait also need runners:read, which the CLI’s login token always has.

List and inspect

manage-runners runner list
manage-runners runner get --id 812999368881481087
manage-runners runner get --name build-01

Runner IDs are long numbers; treat them as strings. runner get --name needs an exact, unique name. The state field uses the API names: CREATING, CONFIGURING, READY (shown as Active in the dashboard), PAUSED, UNKNOWN and INVALID_GITLAB_TOKEN. See Runner states.

Find a server type and location

manage-runners product list
manage-runners product get --id hz-cpx31
manage-runners product locations --id hz-cpx31

product locations shows where Hetzner offers the server type and Hetzner’s monthly net price in each location. Use the location code, for example fsn1, with runner create.

To see the SSH keys of your Hetzner project, pass the Hetzner token on standard input or through an environment variable:

manage-runners provider hetzner ssh-keys --token-env HCLOUD_TOKEN

For an existing runner, runner ssh-keys --id <id> lists the keys available with the runner’s stored Hetzner token.

Create a runner

The CLI reads the GitLab runner token and the Hetzner token from environment variables you name, so they never appear on the command line:

export GLRT_TOKEN HCLOUD_TOKEN   # set them from your secret manager

manage-runners runner create \
  --name build-01 \
  --product-id hz-cpx31 \
  --location fsn1 \
  --executor docker \
  --concurrency 8 \
  --gitlab-host https://gitlab.com/ \
  --runner-token-env GLRT_TOKEN \
  --hetzner-token-env HCLOUD_TOKEN \
  --ssh-key-id 1234567 \
  --label role=ci-runner \
  --monitoring on \
  --wait
Flag Required Description
--name yes Letters, numbers, spaces and dashes
--product-id yes From product list
--location yes From product locations
--executor yes docker, dind or shell
--concurrency yes At least 1
--gitlab-host yes Base URL of your GitLab instance
--runner-token-env yes Name of the variable holding the glrt- token
--hetzner-token-env yes Name of the variable holding the Hetzner token
--ssh-key-id no Hetzner SSH key ID, repeatable
--label no Hetzner label, repeatable
--monitoring on|off no Needs an active plan to turn on
--wait no Wait until the runner reaches a final state
--yes no Confirm creating paid resources without a prompt

runner create creates a paid Hetzner server, so it asks create runner build-01 and potentially create paid resources? [y/N]. Pass --yes in scripts. Without a terminal and without --yes, it fails.

Each create request carries an idempotency key. The CLI generates a new one per invocation; pass your own with --idempotency-key (16 to 128 letters, digits, ., _ or -) to make a script’s retry safe. For the same user, organization, route, key and request, the API returns the first result until 24 hours after the first attempt; after that, the same request creates another runner. A different request with the same key, or a retry while the first attempt has not completed, is rejected as a conflict. Check runner list before retrying an old create. See Idempotency and unknown outcomes.

Wait for a runner

--wait on create, update, pause, resume and duplicate waits until the runner reaches READY, PAUSED, UNKNOWN or INVALID_GITLAB_TOKEN, for at most --timeout (default 10 minutes). Check the returned state: reaching UNKNOWN or INVALID_GITLAB_TOKEN ends the wait too.

To wait later or for a specific state:

manage-runners runner wait --id 812999368881481087 --state READY --timeout 15m
manage-runners runner watch --id 812999368881481087

If the wait times out, the command exits with code 7 (TIMEOUT). If it fails for another reason, for example a token without runners:read, it exits with that error. In both cases the change itself was accepted and is not undone; check the runner with runner get.

Update a runner

manage-runners runner update --id 812999368881481087 --name build-02 --label role=ci-runner
manage-runners runner update --id 812999368881481087 --concurrency 4 --yes --wait

Flags you leave out keep their current values. --label and --ssh-key-id replace the whole list.

--product-id, --gitlab-host, --runner-token-env, --concurrency, --executor, --ssh-key-id and --monitoring recreate an active runner’s server and interrupt its jobs. The CLI asks for confirmation, or needs --yes. --name, --label and --hetzner-token-env alone never recreate the server. See Changes that recreate the server.

The location cannot be changed.

Pause, resume and duplicate

manage-runners runner pause --id 812999368881481087 --yes
manage-runners runner resume --id 812999368881481087 --yes --wait
manage-runners runner duplicate --id 812999368881481087 --yes

pause deletes the server and keeps the IP addresses. resume and duplicate create servers and may cost money. All three ask for confirmation unless you pass --yes.

Delete a runner

Deleting needs the runners:delete scope and the runner ID twice, as --id and --confirm:

manage-runners runner delete --id 812999368881481087 --confirm 812999368881481087 --yes

Interactively, the CLI asks you to confirm and to type the ID. Delete removes the server, the IP addresses and the runner’s schedule. It does not remove the runner from GitLab.

If deletion cannot be confirmed with Hetzner, the runner is kept and the command fails. Run it again later.

Forget an Unknown runner

runner forget only works on a runner in the UNKNOWN state. It removes the record from Manage Runners and leaves any server and IP addresses in Hetzner untouched. It needs the runners:forget scope and the same confirmation as delete:

manage-runners runner forget --id 812999368881481087 --confirm 812999368881481087 --yes

Never use forget instead of delete. Try recovering the runner first, and clean up Hetzner yourself afterwards.

Read monitoring data

manage-runners runner metrics
manage-runners runner metrics --name build-01 --range 7d --output json

Without --id or --name, runner metrics covers every active runner with monitoring on. --range accepts 1h, 24h (default), 7d, 30d and 90d. Human output shows the latest CPU, RAM and disk usage and a summary of the history; JSON includes the full history.

  • latest is empty until the first sample arrives, and stale marks samples older than three minutes.
  • A runner that is not monitored or not active fails with NOT_MONITORED (exit code 5).
  • Without an active plan, the command fails with SUBSCRIPTION_REQUIRED (exit code 4).
  • If the history is shorter than the range, a RANGE_EXCEEDS_HISTORY warning lists the available ranges.

If a change has an unknown outcome

If the connection fails while a change is in progress, the CLI exits with code 10 (UNKNOWN_OUTCOME). The change may or may not have happened. Check the runner with runner list or runner get before you try again; never retry blindly.