Skip to content

Kubernetes

The image is built by apps/runner/Dockerfile, with the repository root as context. The entrypoint is apps/runner/docker-entrypoint.sh and the command is:

node --import @rootpilot/otel/register apps/runner/dist/index.js

The image runs as root, which is why Compose mounts ~/.aws at /root/.aws. If your cluster requires runAsNonRoot, that adaptation is on you, and it has not been done here.

The runner is an outbound client. It listens on no port, exposes no /healthz, and has no readiness endpoint.

The direct consequence: do not configure httpGet liveness or readiness probes: every one of them will fail. If you need a signal, it comes from the boot logs (boot: ready) and from the control-plane’s Fleet view, which sees the runner connected. Per-connector health is also reported in the handshake, not over HTTP.

Requirement 2: termination grace longer than the drain

Section titled “Requirement 2: termination grace longer than the drain”

The runner handles SIGTERM by draining: it stops accepting new work, finishes what’s in flight, and only then exits. The ceiling is RUNNER_DRAIN_TIMEOUT_MS, which defaults to 120,000 ms.

Kubernetes defaults terminationGracePeriodSeconds to 30 s. Leave it there and SIGKILL arrives mid-drain.

terminationGracePeriodSeconds: 150 # > RUNNER_DRAIN_TIMEOUT_MS (120s), with headroom

A hard reclaim with no drain (SIGKILL, OOM, spot) loses learned state since the last flush. RUNNER_LEARNED_FLUSH_INTERVAL_MS (default 60 s) exists to bound that loss, but it doesn’t eliminate it.

The runner is cattle and holds no durable state. The learned-store SQLite cache is :memory: by default. Do not create a PVC. If you point RUNNER_LEARNED_STORE_PATH at a path on disk, know that you’re creating a cache, not a source of truth: the source of truth is the control-plane.

Requirement 4: identity with no secret in the image

Section titled “Requirement 4: identity with no secret in the image”

This is where Kubernetes changes the design, and for the better.

The runner reads the projected OIDC token of the Kubernetes ServiceAccount and sends it as attestation. Nothing secret enters the image: the token is a short-lived, audience-bound JWT the kubelet projects.

The default path is /var/run/secrets/tokens/gke-oidc/token, configurable via RUNNER_GKE_TOKEN_PATH. So the pod needs a projected volume with a serviceAccountToken mounted exactly there.

If the volume isn’t mounted, the error says so explicitly:

RUNNER_ATTESTATION_MODE=gke-oidc: failed to read projected KSA token at
/var/run/secrets/tokens/gke-oidc/token (is the Workload Identity token volume mounted?)

aws-sts signs a GetCallerIdentity request using the SDK’s default credential chain, which on EKS is IRSA. That means an annotated ServiceAccount with an IAM role is enough, with no bootstrap token and no human in a browser.

It requires AWS_REGION explicitly. Without it, boot fails with RUNNER_ATTESTATION_MODE=aws-sts requires AWS_REGION. The region is not guessed on purpose, because the control-plane only accepts regions from its own allowlist, and a guess would surface as an attestation_invalid that reads like a credential problem.

Prefer the secret manager you already operate over Kubernetes Secrets: RUNNER_SECRET_STORE_MODE=aws (AWS Secrets Manager) or vault (HashiCorp Vault KV v2). Both resolve through workload identity, with no static keys. See Secret stores.

For AWS read credentials, RUNNER_AWS_CRED_MODE=chain makes the SDK resolve through IRSA directly, with no key passing through the cluster at all.

Outbound only, nothing inbound. If you use NetworkPolicy, the destination list is in Requirements. Remember that the allowlist declared in connector manifests is documentation, not enforcement: for native connectors, the cluster is what enforces egress.

What’s still missing for a chart to exist

Section titled “What’s still missing for a chart to exist”

Being explicit about the gap, since the question will come up: a real chart would need decisions this repository hasn’t made: runAsNonRoot and moving off /root/.aws, resources values (there is no versioned benchmark), and a canonical way to express the attestation choice per provider. Until those exist, writing the manifests on the installer’s side is the honest path.