Skip to Content
ReferenceRunner Helm chart

Runner Helm chart

The runner is the part of TruStacks that runs in your cluster. You install it from a signed OCI Helm chart:

oci://ghcr.io/trustacks/charts/runner

The published chart has the hosted defaults baked in: SaaS mode on, the control plane at https://api.trustacks.com, and the runner image of the same version pinned. You add one value, your enrollment token.

Requirements: Kubernetes 1.28 or later, Helm 3.14 or later (for --reset-then-reuse-values), and outbound HTTPS from the runner’s namespace.

Install

Generate an enrollment token under Runners and copy the command the page shows. It has this shape; the commands on this page use three shell variables, and 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 the command from the app rather than from this page. The app fills in the version the hosted control plane currently advertises. See Versioning.

Then watch it start and enroll. 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

The Runners page shows the runner as connected once it has enrolled.

Runners, showing a connected runner with its running and published versions

The enrollment token

  • Reusable, does not expire. The runner presents it again each time it renews its session, about every 15 minutes. Treat it like a password.

  • Revoking it. Revoke and generate a new token on Settings → Runners revokes every token in the workspace and mints one replacement. A revoked token can never enroll a new runner. Runners on 0.2.14-rc26 or later keep renewing on their own registered key (the session is sealed to that key); older runners disconnect at their next renewal until they get the new token. The app gives you the command:

    NEW_TOKEN=... # the replacement token the app showed you once helm upgrade trustacks-runner oci://ghcr.io/trustacks/charts/runner \ --version "$VERSION" \ -n "$NAMESPACE" --reset-then-reuse-values \ --set saas.enrollmentToken="$NEW_TOKEN"

    If you manage the Secret yourself, update the key named by saas.enrollmentTokenSecret.key (enrollment-token by default) instead. The chart mounts the Secret as a volume, so a running pod reads the new value at its next renewal. Deleting a runner on the Runners page does not retire the token, and while the token is valid the deleted runner enrolls again at its next renewal. To cut off one runner, revoke, then delete it. See Revoke enrollment tokens.

  • Where it lives. With --set saas.enrollmentToken=... the chart creates the Secret trustacks-runner-enrollment for you, and the token is also stored in the Helm release. To keep it out of the release, create the Secret yourself and leave saas.enrollmentToken empty:

    kubectl -n "$NAMESPACE" create secret generic trustacks-runner-enrollment \ --from-literal=enrollment-token="$TOKEN"

Values

The values you are likely to set. Everything else has a working default.

saas.*

ValuePublished defaultMeaning
saas.enabledtruePull mode: enroll with the hosted control plane and long-poll for work.
saas.enrollmentToken""Your token. When set, the chart creates the token Secret.
saas.enrollmentTokenSecret.nametrustacks-runner-enrollmentSecret holding the token.
saas.enrollmentTokenSecret.keyenrollment-tokenKey inside that Secret.
env.TRUSTACKS_RUNNER_CONTROL_PLANE_URLhttps://api.trustacks.comControl plane the runner calls.

observer.*

Optional. When on, the runner watches your ArgoCD Applications read-only and reports deploy status, so TruStacks can tell you whether a merged change came up healthy.

ValueDefaultMeaning
observer.enabledfalseTurn on the read-only ArgoCD watch.
observer.argocdNamespaceargocdNamespace where your ArgoCD Application resources live.
--set observer.enabled=true --set observer.argocdNamespace=argocd

mcpSignals.*

Optional, off by default, runner 0.2.14-rc27 or later. When on, the runner observes your GitHub repos read-only through GitHub’s official MCP server and reports structured facts (Dependabot alerts: disabled, CI ran green 3h ago) to the Stack screen, and open alerts to Signals. Hosted (SaaS) mode only: the chart refuses to render mcpSignals.enabled=true without saas.enabled=true.

ValueDefaultMeaning
mcpSignals.enabledfalseTurn on MCP Signals. A source also needs its own enabled.
mcpSignals.sweepIntervalSeconds21600 (6 hours)Time between sweeps. The runner enforces a 300-second floor. The first sweep runs when the pod starts.
mcpSignals.sources.github.enabledfalseTurn on the GitHub source.
mcpSignals.sources.github.credentialSecrettrustacks-mcp-githubThe Secret holding your token. You create it; the chart never does.
mcpSignals.sources.github.credentialKeytokenKey inside that Secret.
mcpSignals.sources.github.lockdowntrueGitHub’s lockdown mode: hides public-repo content from users without push access. A best-effort content filter, not a boundary. On, and not relied on.
mcpSignals.sources.github.extraRepos[]Repos to observe beyond the ones you connected in TruStacks, for example ["acme/orders-api"]. Inventory only: they show on Stack but never become Signals findings.

The token. A fine-grained personal access token, resource owner the user or organization that owns the repos, Only select repositories, and these repository permissions, all Read-only: Actions, Contents, Metadata, Pull requests, Issues, Dependabot alerts, Code scanning alerts, Secret scanning alerts. Nothing else, and no write permission. Create the Secret yourself, keeping the token out of your shell history and out of process arguments (printf is a shell builtin; --from-literal would put the token in kubectl’s arguments, visible to anyone who can run ps):

read -rs GH_TOKEN && \ printf '%s' "$GH_TOKEN" | kubectl -n "$NAMESPACE" create secret generic trustacks-mcp-github \ --from-file=token=/dev/stdin && unset GH_TOKEN

Then turn the source on:

helm upgrade trustacks-runner oci://ghcr.io/trustacks/charts/runner \ --version "$VERSION" \ -n "$NAMESPACE" --reset-then-reuse-values \ --set mcpSignals.enabled=true \ --set mcpSignals.sources.github.enabled=true

Check the first sweep:

kubectl -n "$NAMESPACE" logs deploy/trustacks-runner-runner | grep -E 'mcp.sweep_done|mcp.source_refused'

How the credential is handled. The Secret is mounted into the runner pod as a read-only file, not an environment variable. The runner reads it when it starts the MCP server and passes it only to that child process’s environment. It is never logged (the server’s stderr is redacted for token shapes and for the literal value), never sent to TruStacks, and never a tool argument.

What runs. The GitHub MCP server binary ships inside the runner image, pinned by digest, so it is in the runner’s SBOM and under its cosign signature; nothing is pulled at run time. Each sweep starts it as a short-lived child process in read-only mode, limited to the read tools on our allowlist. If the server offers any tool that is not marked read-only or not on the allowlist, the runner refuses to start the source. See Integration model for the full safety model.

Egress. The GitHub source needs HTTPS (443) to api.github.com, which the runner already reaches for your repos. A Kubernetes NetworkPolicy cannot match a hostname: under default-deny egress, use your CNI’s FQDN policy (Cilium toFQDNs, Calico DNS policy) or allow the api CIDRs GitHub publishes at https://api.github.com/meta.

policyBundle.*

Where the runner gets the constitution, and how it verifies it. See Policy in force for what happens at start.

ValuePublished default (0.2.14 and later)Meaning
policyBundle.bundleRefghcr.io/trustacks/policy/constitution:1The signed constitution to pull.
policyBundle.verifytrueVerify the signature before loading. Turning this off loads the bundle unverified and the runner reports it as unverified.
policyBundle.insecurefalsePlain HTTP to the registry. Local development only.
policyBundle.keyless.certificateIdentityRegexptrustacks-policy’s publish.yml only, from 0.2.14-rc26 (rc24–rc25 also accepted the retired trustacks-mvp signer; see Supply-chain verification)Which workflow may have signed the bundle.
policyBundle.keyless.certificateOidcIssuerhttps://token.actions.githubusercontent.comOIDC issuer for the signing certificate.
policyBundle.overlayRef""Overlay bundle. Not supported on hosted yet.
⚠️

Runner charts before 0.2.14-rc24 have no keyless block and point bundleRef at a development registry that does not exist in your cluster. Those runners fall back to the policy baked into the image. The first stable release with these defaults is 0.2.14.

Pod settings

ValueDefault
image.repository, image.tagghcr.io/trustacks/runner, the chart’s version
deployKeyLoader.imagealpine/k8s:1.31.4 (from Docker Hub)
imagePullSecretsEmpty. Set if you mirror the images to a private registry.
resources.requestscpu: 50m, memory: 128Mi
resources.limitscpu: 500m, memory: 512Mi
podSecurityContextrunAsNonRoot: true, runAsUser: 10001, fsGroup: 10001
securityContextNo privilege escalation, read-only root filesystem, all capabilities dropped
nodeSelector, tolerations, affinityEmpty

What the chart creates

ResourceName (release trustacks-runner)
Deployment, Service (ClusterIP, port 8000), ServiceAccounttrustacks-runner-runner
Secret, when you pass the tokentrustacks-runner-enrollment
Role and RoleBindingtrustacks-runner-runner-deploy-keys, trustacks-runner-runner-secret-writer
ClusterRole and RoleBinding, when the observer is ontrustacks-runner-runner-argocd-observer

The runner writes two Secrets of its own at runtime. Helm does not manage them:

  • trustacks-runner-identity · the runner’s keypair and ID, so a replaced pod re-enrolls as the same runner.
  • trustacks-runner-llm · your LLM key, unsealed from what the control plane delivered.

Permissions

All namespace-scoped to the runner’s namespace unless stated.

GrantVerbsWhy
Secretsget, listAn init container loads git deploy keys labeled trustacks.io/role=git-deploy-key (used by local development)
Secretsget, create, updateThe runner writes its identity and LLM Secrets
applications.argoproj.io in observer.argocdNamespaceget, list, watchObserver only. Read-only: the runner cannot sync, roll back, or delete.

No cluster-wide grants. The observer’s ClusterRole is bound with a RoleBinding in your ArgoCD namespace only.

Network

The runner opens no inbound ports to the internet; the ClusterIP Service is reachable only inside your cluster. It needs outbound HTTPS to:

DestinationWhy
api.trustacks.comEnrollment, job polling, results
ghcr.io (and its blob storage)Runner image, chart, policy bundle
docker.ioThe alpine/k8s image used by the deploy-key init container
api.anthropic.com or openrouter.aiYour LLM provider
github.com, api.github.comClone your repos, push the proposal branch, open the PR; with MCP Signals on, the read-only GitHub observations
Sigstore’s TUF and Rekor endpoints (tuf-repo-cdn.sigstore.dev, rekor.sigstore.dev)Keyless signature verification of the policy bundle

There is no mTLS tunnel and nothing to allow inbound.

Upgrade

The Runners page shows the published version and the version your runner is running, and generates the upgrade command:

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

--reset-then-reuse-values takes the new chart’s defaults, including the new runner image, then reapplies the values you set yourself (for example saas.enrollmentToken if you passed it on the command line, and any observer settings).

🚫

Never upgrade with --reuse-values. It reuses every value from the previous release, including the old image tag baked into the old chart. The upgrade reports success and the old runner keeps running. Confirm the running version on the Runners page after every upgrade.

Uninstall

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

The second command removes the Secrets the runner wrote itself, which Helm does not track. Then delete the runner on Runners. Removing the TruStacks GitHub App from your org is separate, in your GitHub settings.

⚠️

Uninstalling does not revoke the enrollment token. It is reusable and does not expire. Delete your copies of it, and if it may have leaked, revoke it on Runners with Revoke and generate a new token; see Revoke enrollment tokens.

Verify before you install

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"

The same identity signs the runner image. See Supply-chain verification.

Where to go next

Last updated on