Skip to content

Configuration Reference

Complete listing of configuration sources, YAML keys, environment variables, CLI flags, and the systemd unit's configuration-related facts for incus-gh-runner.

Sources and precedence

Controller-mode configuration is assembled from four sources. Precedence, highest first:

  1. CLI flags
  2. Environment variables (INCUS_GH_RUNNER_ prefix)
  3. YAML configuration file
  4. Built-in defaults

Each key is resolved independently, so different keys in the same run may come from different sources.

The default configuration file path is /etc/incus-gh-runner/config.yaml. This path is optional: if no file exists there, startup continues using environment variables, flags, and defaults. An explicit --config path is not optional — if the file does not exist or cannot be read, the process exits with an error before startup completes.

Configuration files are decoded exactly before source precedence is applied. Unknown or duplicate keys, misspellings, wrong YAML scalar or container types, aliases, merge keys, and multiple YAML documents are rejected. Field-level errors identify the field; validation errors do not include field values.

Migration: the controller no longer selects an implicit socket

Exactly one of incus.socket and incus.url is required in controller mode. Configurations that previously omitted incus.socket must add the intended path explicitly. This is an intentional breaking change: only the standalone validate subcommand retains an implicit socket default.

YAML configuration keys

Duration values use Go duration syntax (for example 30s, 5m).

github

Key Type Default Required / validation
github.config_url string — Required. Absolute HTTPS GitHub or GHES organization or repository URL. GitHub-hosted domains may not specify a port; GHES ports must be valid. Enterprise URLs, trailing DNS dots, userinfo, query strings, fragments, escaped paths, and other path shapes are rejected.
github.scale_set string — Required. Non-empty and exactly representable by the GitHub client without query decoding. Also used as the sole runner label. A pre-existing scale set must have that exact label and runner self-update disabled.
github.runner_group string default Controller validation requires a non-empty, exactly representable value, default at repository scope, and an explicit non-default name at organization scope. The controller does not enforce the group's repository or workflow policy; restricting the organization group to selected repositories and commit-pinned workflows is a separate GitHub-side control (see Deploy).
github.message_poll_timeout duration 0s Optional workaround for actions/scaleset#122. 0s preserves the upstream long poll. A nonzero value must be at least 5s; when it expires, the controller treats that poll as an empty response and immediately opens the next poll.
github.token_file string — PAT file read once at startup. Mutually exclusive with github.app and INCUS_GH_RUNNER_GITHUB_TOKEN.
github.app.client_id string — Required if GitHub App credentials are configured.
github.app.installation_id int64 — Required if GitHub App credentials are configured. Must be greater than 0.
github.app.private_key_file string — Required if GitHub App credentials are configured. Path to a PEM file, read once at startup.

Temporary workaround increases message API traffic

At 5s, an idle controller can make up to about 12 message requests per minute instead of about 1.2 with the upstream 50-second long poll. Enable this only while the upstream issue remains unresolved, and remove the override once the client or service wakes active polls reliably.

incus

Key Type Default Required / validation
incus.socket string "" Required for local mode. Unix socket path. Mutually exclusive with incus.url and all three HTTPS file settings.
incus.url string "" Required for HTTPS mode. Absolute HTTPS Incus API root endpoint. Mutually exclusive with incus.socket; plaintext HTTP and URL user information are rejected.
incus.client_cert_file string "" Required with incus.url; invalid with incus.socket. PEM TLS client certificate path.
incus.client_key_file string "" Required with incus.url; invalid with incus.socket. PEM TLS client private-key path.
incus.server_cert_file string "" Required with incus.url; invalid with incus.socket. PEM certificate whose exact leaf certificate must be presented by the Incus server.
incus.project string — Required. Non-empty. Must already exist and be directly accessible to the configured Incus identity.
incus.image string — Required. Non-empty. Existing local image alias or fingerprint, resolved to a full fingerprint during preflight.
incus.profiles list of strings [] Optional. No empty entries. Profiles must already exist. Their effective configuration and devices are pinned during preflight and materialized directly onto each VM. When omitted, Incus image/default profile selection is reproduced before pinning.
incus.owner string — Required. Non-empty. Exact cleanup selector written to every instance this process manages; not an authorization boundary.
incus.bootstrap_timeout duration 5m Must be greater than 0.
incus.diagnostics_dir string "" Optional. Directory for terminal-runner serial console diagnostics. Persistence is disabled when empty.

For HTTPS, the client performs normal certificate-chain, hostname, validity, and key-usage verification and also requires the presented leaf certificate to equal incus.server_cert_file. The same pin applies to HTTP and WebSocket connections, and a redirect cannot downgrade the connection to plaintext HTTP. A wrong, expired, untrusted, or hostname-invalid certificate fails the connection; there is no insecure or trust-on-first-use fallback.

During startup, the controller reads and parses the client certificate, client key, and server certificate inside the bounded initial Incus connection. It first confirms direct access to incus.project, then selects that project. The resulting SDK client is rebound to the retained process context before image and profile preflight. Certificate files are not watched or reloaded; restart the service after rotating any of them. File-read and parse errors identify the setting without including certificate or private-key contents.

Sensitive console diagnostics

Content written to incus.diagnostics_dir can include workload console output. Restrict access to this directory accordingly.

Each capture is limited to 1 MiB and the directory sink retains at most 256 capture files. The packaged tmpfiles policy expires files older than 30 days from /var/log/incus-gh-runner/diagnostics; deployments using another directory must update that policy path.

job_proof

Job machine proofs are disabled when this section is absent. The two settings must be configured together; setting only one fails controller startup.

When enabled, the controller attempts one signed proof for every valid GitHub JobStarted event on its resolved scale set; the job proofs reference documents the proof format. Events enter a non-blocking queue whose capacity is max(1, capacity.max_runners); malformed events or a full queue produce no proof and do not interrupt runner demand tracking. The guest helper times out when no committed proof arrives.

Key Type Default Required / validation
job_proof.host_id string "" Stable host identity recorded in every proof and enrolled beside the public key. Required when job_proof.signing_key_file is set.
job_proof.signing_key_file string "" Regular file no larger than 16 KiB containing exactly one PKCS#8 PEM Ed25519 private key. Read once during startup. Required when job_proof.host_id is set.

The upstream listener acknowledges a queue message before invoking the job callback. A controller crash in that interval or before proof delivery can leave a running job without a proof; version 1 does not persist or reconstruct events after restart, and the guest helper's timeout is the only signal a job receives that no proof was committed.

capacity

Key Type Default Required / validation
capacity.min_runners int 0 Must be >= 0.
capacity.max_runners int 1 Must be >= capacity.min_runners.

concurrency

Key Type Default Required / validation
concurrency.incus_operations int 2 Must be >= 1.

timeouts

Key Type Default Required / validation
timeouts.incus_operation duration 5m Must be greater than 0.
timeouts.shutdown duration 30s Must be greater than 0.

retry

Key Type Default Required / validation
retry.initial duration 1s Must be greater than 0.
retry.maximum duration 30s Must be >= retry.initial.

Top-level

Key Type Default Required / validation
reconcile_interval duration 1s Must be greater than 0.

Environment variables

Every scalar YAML key binds to an environment variable named INCUS_GH_RUNNER_ followed by the key path, uppercased, with . and _ both rendered as _. The list-valued incus.profiles key is YAML-only.

Examples:

YAML key Environment variable
github.config_url INCUS_GH_RUNNER_GITHUB_CONFIG_URL
github.message_poll_timeout INCUS_GH_RUNNER_GITHUB_MESSAGE_POLL_TIMEOUT
incus.socket INCUS_GH_RUNNER_INCUS_SOCKET
incus.url INCUS_GH_RUNNER_INCUS_URL
incus.client_cert_file INCUS_GH_RUNNER_INCUS_CLIENT_CERT_FILE
incus.client_key_file INCUS_GH_RUNNER_INCUS_CLIENT_KEY_FILE
incus.server_cert_file INCUS_GH_RUNNER_INCUS_SERVER_CERT_FILE
incus.project INCUS_GH_RUNNER_INCUS_PROJECT
capacity.min_runners INCUS_GH_RUNNER_CAPACITY_MIN_RUNNERS
timeouts.shutdown INCUS_GH_RUNNER_TIMEOUTS_SHUTDOWN
github.token_file INCUS_GH_RUNNER_GITHUB_TOKEN_FILE
github.app.private_key_file INCUS_GH_RUNNER_GITHUB_APP_PRIVATE_KEY_FILE
job_proof.host_id INCUS_GH_RUNNER_JOB_PROOF_HOST_ID
job_proof.signing_key_file INCUS_GH_RUNNER_JOB_PROOF_SIGNING_KEY_FILE

INCUS_GH_RUNNER_GITHUB_TOKEN

Environment-only GitHub personal access token. This variable:

  • Is never read from the YAML file (there is no github.token YAML key).
  • Has no corresponding CLI flag.
  • Is trimmed of surrounding whitespace before use.

The packaged systemd deployment supplies the PAT through github.token_file via the PAT credential drop-in. A raw environment value serves local or externally supervised execution where the environment is already the credential boundary.

github.token_file

Path to a file containing a GitHub personal access token. The controller reads and trims the file once during startup; a missing, unreadable, or empty file fails startup. The packaged PAT drop-in sets INCUS_GH_RUNNER_GITHUB_TOKEN_FILE to systemd's protected runtime credential copy, so the path should be absent from config.yaml in that deployment.

Incus HTTPS files

The packaged HTTPS deployment keeps client_cert_file and server_cert_file in readable public-certificate files under /etc/incus-gh-runner. It keeps the client private-key source root-owned and mode 0600. The credentials-incus-https.conf drop-in loads that source into systemd's protected runtime credential directory and sets INCUS_GH_RUNNER_INCUS_CLIENT_KEY_FILE=%d/incus-client-key; omit client_key_file from config.yaml when using this drop-in.

Obtain the pinned server certificate through an authenticated out-of-band channel and compare its fingerprint with a trusted operator record. Do not bootstrap the pin with an insecure request or blind trust on first use. Ciphers and certificate trust are not relaxed when the exact pin is present. Restart the service after replacing the client certificate, client key, or server pin.

GitHub credential rule

Exactly one credential source must be configured:

  • GitHub App — all three of github.app.client_id, github.app.installation_id, and github.app.private_key_file must be set.
  • Personal access token file — github.token_file or INCUS_GH_RUNNER_GITHUB_TOKEN_FILE must identify the protected token file.
  • Personal access token value — INCUS_GH_RUNNER_GITHUB_TOKEN must be set.

Configuring more than one method is an error, including setting both PAT sources. Configuring no credential is also an error.

Incus authority remains security-critical

Local incus-admin socket access is root-equivalent on the compute host. An unrestricted Incus TLS client certificate also has administrative authority; HTTPS alone does not restrict it. Use a TLS certificate restricted to the runner project for the controller, retain the restricted project and workload-isolation controls, and use a dedicated, single-purpose compute host. The incus.owner marker limits intended cleanup but is forgeable by another project writer and is not authorization.

The packaged systemd deployment supplies either github.app.private_key_file or github.token_file through one selected GitHub credential drop-in. HTTPS mode additionally supplies incus.client_key_file through its independent Incus credential drop-in. Secret values do not belong in config.yaml. See systemd unit facts.

CLI

Running incus-gh-runner without a subcommand starts the controller. The controller configuration sources and flags documented above apply to that mode. The validate and proof verify subcommands are independent of controller configuration and GitHub credentials.

--config

Flag Type Default
--config string ""

Configuration file path. Empty selects the default path (/etc/incus-gh-runner/config.yaml, optional). A non-empty value must point to an existing, readable file.

Configuration flags

Each flag overrides its corresponding YAML key and environment variable.

Flag Type Default Configuration key
--min-runners int 0 capacity.min_runners
--max-runners int 1 capacity.max_runners
--incus-operations int 2 concurrency.incus_operations
--reconcile-interval duration 1s reconcile_interval
--operation-timeout duration 5m timeouts.incus_operation
--shutdown-timeout duration 30s timeouts.shutdown

--version

Prints build metadata and exits:

incus-gh-runner <version> (<commit>) built <date>

In a release build, <version>, <commit>, and <date> are populated at build time. In a development build, these render as dev, none, and unknown respectively.

validate <baseline>

Validates exactly one rendered JSON baseline against the embedded CUE policy, then compares it with effective state read through one flag-selected Incus connection. The command performs GET operations only; it never creates, changes, or deletes Incus resources and does not invoke external cue, incus, or jq executables.

Flag Type Default
--socket string /var/lib/incus/unix.socket
--url string ""
--client-cert string ""
--client-key string ""
--server-cert string ""

With no connection flags, validate uses /var/lib/incus/unix.socket. An explicit --socket selects another local socket. An explicit --url clears the implicit socket and requires all three certificate flags. Supplying both --socket and --url is an error.

These are flag-only validator inputs. The command does not load controller YAML, controller environment bindings, controller flags, or GitHub credentials. The HTTPS certificate behavior is the same strict normal TLS verification plus exact server-leaf pin used by controller mode. A successful validation prints one human-readable line to stdout; compatibility notices are written to stderr.

The validator reads the Incus server configuration, including sensitive listener values, plus the named runner project, the network and ACL in the default project, the runner-project profile, and the global storage pool. Its operator credential must have an administrative view that exposes every one of those resources and values; hidden sensitive fields fail validation. Keep that credential separate from the project-restricted controller certificate. Do not broaden the controller certificate to make validation pass.

The live comparison confirms effective resource ceilings but cannot re-measure or re-prove the physical-host capacity and reserved headroom used to generate them. Re-render and review the baseline after those generation-time inputs change.

proof verify <proof>

Authenticates one DSSE job machine proof against an enrolled Ed25519 public key and expected host identity:

incus-gh-runner proof verify \
  --public-key machine-provenance-key.pub.pem \
  --expected-host-id builder-host-01 \
  job-proof.dsse.json > verified-payload.json
Flag Type Default
--public-key string required
--expected-host-id string required

The command reads no controller configuration and never loads the signing key. It limits the envelope and decoded payload to 64 KiB, requires the exact version 1 payload type and one signature, rejects unknown payload fields, and prints only verified payload JSON to stdout. Repository, workflow, image, and freshness policy are deliberately left to the caller. Any parse, signature, key, schema, or host mismatch exits non-zero without printing unverified payload bytes. The job proofs reference documents the envelope, the version 1 payload fields, and the key-ID rule the command enforces.

Exit behavior

Controller mode exits 0 on clean shutdown. It exits 1 on configuration load failure, configuration validation failure, or runtime failure, printing the error to stderr. There is no flag to control controller log level or log format; controller logs are always structured JSON on stdout. validate exits 0 only when policy and live-state checks pass and exits non-zero with an error on stderr otherwise.

systemd unit facts

The packaged base unit is deploy/systemd/incus-gh-runner.service. It deliberately selects no GitHub credential method. Install exactly one packaged GitHub credential drop-in as /etc/systemd/system/incus-gh-runner.service.d/credentials.conf:

  • credentials-github-app.conf loads /etc/incus-gh-runner/github-app-private-key.pem and sets INCUS_GH_RUNNER_GITHUB_APP_PRIVATE_KEY_FILE.
  • credentials-personal-access-token.conf loads /etc/incus-gh-runner/github-token and sets INCUS_GH_RUNNER_GITHUB_TOKEN_FILE.

For HTTPS mode, also install credentials-incus-https.conf as incus-https.conf. It clears the base unit's SupplementaryGroups value, loads the root-only /etc/incus-gh-runner/client.key, and sets INCUS_GH_RUNNER_INCUS_CLIENT_KEY_FILE to the protected runtime copy. The DynamicUser needs read access to the public client and server certificate files. It does not need incus-admin membership in HTTPS mode.

When job proofs are enabled, install exactly one independent proof-key drop-in as job-proof.conf:

  • credentials-job-proof-file.conf loads the root-only plaintext source with LoadCredential=.
  • credentials-job-proof-tpm.conf decrypts the TPM-bound source with LoadCredentialEncrypted=.

Both set INCUS_GH_RUNNER_JOB_PROOF_SIGNING_KEY_FILE to the same protected runtime file (%d/machine-provenance-key), so they compose with either GitHub credential method and either Incus connection mode.

Directive Value
ExecStart /usr/bin/incus-gh-runner --config /etc/incus-gh-runner/config.yaml
GitHub credential Selected by one systemd drop-in; absent from the base unit
Incus client key Selected by credentials-incus-https.conf in HTTPS mode; absent in socket mode
ConfigurationDirectory incus-gh-runner (mode 0755), resolves to /etc/incus-gh-runner
LogsDirectory incus-gh-runner (mode 0700), resolves to /var/log/incus-gh-runner
DynamicUser yes
SupplementaryGroups incus-admin in the base socket-mode unit; cleared by the HTTPS drop-in
UMask 0077
Restart on-failure
RestartSec 5s
StartLimitIntervalSec / StartLimitBurst 60s / 5
KillSignal SIGTERM
TimeoutStopSec 70s

TimeoutStopSec must exceed the application's internal shutdown budget of 2 × timeouts.shutdown. At the default timeouts.shutdown of 30s, that budget is 60s, under the unit's 70s stop timeout. Raising timeouts.shutdown requires raising TimeoutStopSec to keep TimeoutStopSec > 2 × timeouts.shutdown. See How incus-gh-runner works for the shutdown model these settings drive.

Hardening directives

The unit sets the following sandboxing directives: NoNewPrivileges, ProtectSystem=strict, ProtectHome, PrivateTmp, PrivateDevices, ProtectKernelTunables, ProtectKernelModules, ProtectKernelLogs, ProtectControlGroups, ProtectClock, ProtectHostname, ProtectProc=invisible, RestrictNamespaces, RestrictRealtime, RestrictSUIDSGID, LockPersonality, MemoryDenyWriteExecute, SystemCallArchitectures=native, empty CapabilityBoundingSet, empty AmbientCapabilities, and RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6.

See also

  • How incus-gh-runner works — capacity formula, lifecycle states, and the shutdown model these settings drive.
  • Deploy — end-to-end production deployment using this configuration surface.
  • Operate — day-2 operational use of these settings.
  • Guest contract — schemas and metadata keys not covered by this page.
  • Job proofs — the proof envelope, payload schema, key-ID rule, and enrollment facts.