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
kubeVersionis>=1.28.0-0). Any conformant distribution, including managed ones and laptop clusters. - Nodes on
linux/amd64orlinux/arm64. helm3.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 enrollA 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
| Value | Default in the published chart | What it does |
|---|---|---|
saas.enrollmentToken | empty | Token to put in a chart-created Secret |
saas.enrollmentTokenSecret.name / .key | trustacks-runner-enrollment / enrollment-token | Where the runner reads the token |
observer.enabled | false | Read-only ArgoCD deploy-status reporting (see below) |
observer.argocdNamespace | argocd | Namespace your ArgoCD Application resources live in |
mcpSignals.enabled, mcpSignals.sources.github.enabled | false | Read-only GitHub observations for the Stack screen, and open alerts as findings in Signals, with a token you put in a Secret (see mcpSignals.*) |
resources | requests 50m CPU / 128Mi, limits 500m / 512Mi | Pod resources |
imagePullSecrets | none | For pulling through a private mirror |
deployKeyLoader.image | alpine/k8s:1.31.4 | Image for one init container; override to use a mirror |
nodeSelector, tolerations, affinity | empty | Scheduling |
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:
| Grant | Scope | Why |
|---|---|---|
Secrets: get, list | The runner’s namespace | An init container loads labeled credential Secrets at start |
Secrets: get, create, update | The runner’s namespace | The runner writes its own Secrets: the LLM key it receives sealed, and its identity |
ArgoCD Applications: get, list, watch | Your ArgoCD namespace only, and only when observer.enabled=true | Read 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.
| Destination | Why |
|---|---|
api.trustacks.com | Enrollment, 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.io | The alpine/k8s init container image, unless you override deployKeyLoader.image |
github.com, api.github.com | Clone your repos, push branches, open pull requests; with MCP Signals on, the read-only GitHub observations |
api.anthropic.com or openrouter.ai | The LLM provider you configured |
Sigstore: tuf-repo-cdn.sigstore.dev, rekor.sigstore.dev | Keyless 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 statusVersion 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:1Policy 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-valuesDo 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
-
Remove the runner in Runners in the app.
-
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-llmIf you created
trustacks-runner-enrollmentyourself, 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
- Credentials and trust boundary · what each side holds
- GitOps integration · wire ArgoCD to the platform repo
- Runner Helm chart reference · every value