Skip to Content
InstallationRunner install

Runner install

The runner is the one TruStacks component you install. It is a single Helm release in your Kubernetes cluster that calls out to the hosted control plane at api.trustacks.com, pulls work, and runs the agents next to your code. It opens no inbound ports. This page is the reference for installing and operating it; the Quickstart is the guided version.

Requirements

  • Kubernetes 1.28 or newer (the chart’s kubeVersion is >=1.28.0-0). Any conformant distribution, including managed ones and laptop clusters.
  • Nodes on linux/amd64 or linux/arm64.
  • helm 3.14 or newer (the upgrade command needs --reset-then-reuse-values).
  • Permission to create a namespace, a Deployment, a ServiceAccount, Roles and RoleBindings in it, and (only if you enable the observer) a ClusterRole.
  • An enrollment token from Runners in the app.

Install

Generate an enrollment token in Runners and copy the command the app shows. It pins the chart version the control plane currently publishes. The commands on this page use three shell variables; the app’s command has the version and token filled in:

NAMESPACE=trustacks-system # any namespace you choose VERSION=0.0.0 # placeholder: the published version the app shows TOKEN=paste-from-ui # placeholder: the enrollment token the app shows helm install trustacks-runner oci://ghcr.io/trustacks/charts/runner \ --version "$VERSION" \ -n "$NAMESPACE" --create-namespace \ --set saas.enrollmentToken="$TOKEN"

Copy it from the app rather than from here, so the version matches what the hosted service expects. The chart is public; no registry login is needed.

The published chart already has SaaS mode on, the control plane URL set to https://api.trustacks.com, and the image pinned to the matching ghcr.io/trustacks/runner:<version>. The enrollment token is the only value you must set.

The chart names its resources <release>-runner. With the release name trustacks-runner, the Deployment is trustacks-runner-runner:

kubectl -n "$NAMESPACE" rollout status deploy/trustacks-runner-runner kubectl -n "$NAMESPACE" logs deploy/trustacks-runner-runner | grep -i enroll

A healthy runner long-polls the control plane; its log shows repeated 200 responses for GET /api/runner/jobs. Runners in the app shows it as connected, with its version.

The enrollment token

The token ties the runner to your workspace. It is reusable and does not expire: the runner presents it again each time it renews its session (about every 15 minutes), so it must stay available to the pod. On our side it is stored only as a hash. It stops working only when you revoke it, on Runners with Revoke and generate a new token (see Revoke enrollment tokens).

There are two ways to supply it.

From the command line (default). --set saas.enrollmentToken="$TOKEN" makes the chart create a Secret named trustacks-runner-enrollment holding it. The token is then also stored in the Helm release record.

From a Secret you manage. If you don’t want the token in Helm’s release history, or you deliver secrets through your own tooling (External Secrets, Sealed Secrets, Vault), create the Secret yourself and leave saas.enrollmentToken empty:

kubectl -n "$NAMESPACE" create secret generic trustacks-runner-enrollment \ --from-literal=enrollment-token="$TOKEN" helm install trustacks-runner oci://ghcr.io/trustacks/charts/runner \ --version "$VERSION" \ -n "$NAMESPACE"

The chart reads saas.enrollmentTokenSecret.name (default trustacks-runner-enrollment) and saas.enrollmentTokenSecret.key (default enrollment-token). Override either if your Secret uses other names.

Values you may set

ValueDefault in the published chartWhat it does
saas.enrollmentTokenemptyToken to put in a chart-created Secret
saas.enrollmentTokenSecret.name / .keytrustacks-runner-enrollment / enrollment-tokenWhere the runner reads the token
observer.enabledfalseRead-only ArgoCD deploy-status reporting (see below)
observer.argocdNamespaceargocdNamespace your ArgoCD Application resources live in
mcpSignals.enabled, mcpSignals.sources.github.enabledfalseRead-only GitHub observations for the Stack screen, and open alerts as findings in Signals, with a token you put in a Secret (see mcpSignals.*)
resourcesrequests 50m CPU / 128Mi, limits 500m / 512MiPod resources
imagePullSecretsnoneFor pulling through a private mirror
deployKeyLoader.imagealpine/k8s:1.31.4Image for one init container; override to use a mirror
nodeSelector, tolerations, affinityemptyScheduling

Leave the control plane URL, image, and policy settings at the published defaults. The full values list is in the runner chart reference.

What the chart creates

  • One Deployment (<release>-runner), one replica, running as a non-root user (UID 10001) with a read-only root filesystem, no privilege escalation, and all capabilities dropped.
  • A ClusterIP Service on port 8000. No Ingress. Nothing outside the cluster needs to reach it.
  • A ServiceAccount and namespaced RBAC:
GrantScopeWhy
Secrets: get, listThe runner’s namespaceAn init container loads labeled credential Secrets at start
Secrets: get, create, updateThe runner’s namespaceThe runner writes its own Secrets: the LLM key it receives sealed, and its identity
ArgoCD Applications: get, list, watchYour ArgoCD namespace only, and only when observer.enabled=trueRead deploy status. It cannot sync, roll back, or delete

The runner has no cluster-wide permissions and no access to other namespaces (apart from the optional read-only observer grant).

Two Secrets are written by the runner, not by Helm:

  • trustacks-runner-identity: the runner’s keypair and runner ID, so a replaced pod re-enrolls as the same runner instead of appearing as a new one.
  • trustacks-runner-llm: your LLM key, after the runner unseals it.

Network requirements

The runner needs outbound HTTPS (443) only. Nothing inbound.

DestinationWhy
api.trustacks.comEnrollment, jobs, results, sealed credentials
ghcr.io (and its blob storage, pkg-containers.githubusercontent.com)The runner image, the chart, and the policy bundle
registry-1.docker.io and auth.docker.ioThe alpine/k8s init container image, unless you override deployKeyLoader.image
github.com, api.github.comClone your repos, push branches, open pull requests; with MCP Signals on, the read-only GitHub observations
api.anthropic.com or openrouter.aiThe LLM provider you configured
Sigstore: tuf-repo-cdn.sigstore.dev, rekor.sigstore.devKeyless verification of the policy bundle at start-up

If you route egress through a proxy, it must allow these hosts. If the Sigstore hosts are blocked, the runner still starts but falls back to the policy copy built into its image, and reports why (see below).

Policy at start-up

An init container, load-policy-bundle, fetches the signed constitution from ghcr.io/trustacks/policy/constitution, verifies its signature keylessly against the public trustacks-policy publishing workflow, and only then extracts it.

If the pull or the verification fails, the runner uses the copy of the constitution baked into its image instead, and records the reason (pull_failed or verify_failed). It never silently runs an unverified bundle. To see what your runner loaded:

kubectl -n "$NAMESPACE" logs deploy/trustacks-runner-runner -c load-policy-bundle | grep status
⚠️

Version note. Pulling and verifying the published constitution ships in runner 0.2.14-rc24 and later, which are pre-releases. No stable release includes it yet, and the app’s install command uses the newest stable release, so a runner installed from it uses the image-baked copy, unverified at start-up, until a stable release ships the verified pull. See Policy in force.

Verify what you install

Everything you run is signed keylessly with Sigstore by a public GitHub Actions workflow. There is no key file to download: you verify against the workflow identity. Install cosign 2.x or newer.

The chart and the runner image are signed by trustacks-mvp/.github/workflows/publish-images.yml on a release tag:

cosign verify \ --certificate-identity-regexp '^https://github\.com/TruStacks/trustacks-mvp/\.github/workflows/publish-images\.yml@refs/tags/v.*$' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ "ghcr.io/trustacks/charts/runner:$VERSION" cosign verify \ --certificate-identity-regexp '^https://github\.com/TruStacks/trustacks-mvp/\.github/workflows/publish-images\.yml@refs/tags/v.*$' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ "ghcr.io/trustacks/runner:$VERSION"

The policy bundle is signed by the public trustacks-policy repo’s publish workflow:

cosign verify \ --certificate-identity-regexp '^https://github\.com/TruStacks/trustacks-policy/\.github/workflows/publish\.yml@refs/tags/v.*$' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ ghcr.io/trustacks/policy/constitution:1

Policy versions independently of the product (the 1.x line); the runner follows the 1 tag. Bundles published before policy 1.0.0 were signed by trustacks-mvp/.github/workflows/publish-policy.yml. That identity is retired: runners from 0.2.14-rc26 accept only the trustacks-policy identity.

Images also carry an SPDX SBOM and build provenance. More in supply-chain verification.

Upgrade

Runners in the app shows the version your runner is running next to the version currently published, and gives you the upgrade command when they differ. Copy it from there:

VERSION=0.0.0 # placeholder: the published version the app shows helm upgrade trustacks-runner oci://ghcr.io/trustacks/charts/runner \ --version "$VERSION" \ -n "$NAMESPACE" --reset-then-reuse-values
🚫

Do not use --reuse-values. It reuses the previous release’s computed values, which include the old image tag. The upgrade reports success and keeps running the old runner. --reset-then-reuse-values (Helm 3.14+) takes the new chart’s defaults and then reapplies the values you set yourself (for example saas.enrollmentToken, if you passed it on the command line). A token you supply from your own Secret is read by name and is unaffected either way.

Check the running version afterwards, not only Helm’s exit code:

kubectl -n "$NAMESPACE" get deploy trustacks-runner-runner \ -o jsonpath='{.spec.template.spec.containers[0].image}'

Uninstall

  1. Remove the runner in Runners in the app.

  2. Uninstall the release, then delete the Secrets the runner wrote itself (Helm does not track them):

    helm uninstall trustacks-runner -n "$NAMESPACE" kubectl -n "$NAMESPACE" delete secret trustacks-runner-identity trustacks-runner-llm

    If you created trustacks-runner-enrollment yourself, delete it too.

⚠️

Removing a runner does not revoke its enrollment token. Removing it in the app deletes the runner’s registered key, but the token itself stays valid, because tokens are reusable and do not expire. A runner still running with a valid token enrolls again at its next session renewal. Anyone holding a valid token can enroll a runner into your workspace. Delete every copy you hold (Helm values files, CI variables, the Secret, an external secret store). If a token may have leaked, revoke it yourself on Runners with Revoke and generate a new token; see Revoke enrollment tokens.

Where to go next

Last updated on