Validate the Helm Charts

Check the Tektona charts against your cluster before you install them, then confirm that the release works.

Tektona installs on your own cluster from two Helm charts:

  • tektona-control-plane serves the API and the console, and it schedules and builds sandboxes.
  • tektona-runner runs the sandbox virtual machines and their gateways on the nodes that you mark as runners.

This page is a checklist. Each step fails on its own, before the step after it, so run them in order. The first five steps need no cluster changes.

The charts are an early draft

The value names can change between releases. Pin the chart to a release tag, and read the release notes before an upgrade.

Before you start

You needDetail
Helm 3.14 or laterhelm version
Cluster accesskubectl auth can-i create deployment --namespace tektona-system
The chart sourceClone the repository at a release tag. The charts sit under deploy/charts.
A namespacetektona-system in the examples below
Registry credentialsThe images are private. You need a pull secret.
cert-managerNeeded for the gateway certificates. certManager.deploy installs it, in two passes.

Decide four things first. Each one changes the values file:

DecisionOptions
PostgreSQLYour own server, or a cluster that the chart installs. Required either way.
NATSYour own server, or a server that the chart installs
Object storageYour own S3 service, or RustFS that the chart installs
Sign-onFederated through Zitadel, or direct for API access. See Configure Sign-On.
Developer platformfeatures.devPlatform adds the workflow surface. It needs its own database and a Hatchet engine.
SecretsThe tool that lands the credentials in the namespace

The charts never create a secret. They only reference one. Any tool works that writes a normal Kubernetes secret, such as SOPS, Sealed Secrets, or the External Secrets Operator.

Fetch the chart dependencies

The charts install PostgreSQL, NATS, S3 and abuse detection only when you ask for them. Helm still needs the dependency archives before it renders anything.

Add the repositories first. A clean machine has none of them, and the build stops with no repository definition for ...:

helm repo add cnpg https://cloudnative-pg.github.io/charts
helm repo add nats https://nats-io.github.io/k8s/helm/charts/
helm repo add rustfs https://charts.rustfs.com
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo add jetstack https://charts.jetstack.io
helm repo update

helm dependency build ./deploy/charts/tektona-control-plane
helm dependency build ./deploy/charts/tektona-runner

A chart pulled from the registry needs none of this. It carries its dependencies inside the package.

build installs the exact versions from the lock file of the chart. Use helm dependency update only when you want newer versions.

Write the values files

Start from the smallest file that points at services which you already run.

values-control-plane.yaml
# The registry holds no image tagged with the chart version, so a chart built
# from the repository needs the tag. A chart pulled from the registry does not.
global:
  imageTag: "<commit sha>"
  imagePullSecrets:
    - name: "regcred"

domains:
  api: "api.example.com"
  app: "app.example.com"
  sandboxIngress: "sandbox.example.com"
  agentlinkTrustDomain: "agentlink.example.com"
  # The API refuses to start without these two.
  vnc: "vnc.example.com"
  sshWebsocket: "ssh.example.com"
  # The address that the CLI dials for direct SSH access.
  sshHost: "ssh.example.com"

cluster:
  # Must equal location in the runner file.
  sandboxLocation: "eu"

database:
  host: "postgres.example.com"
  name: "platform_api"
  existingSecret: "tektona-database"

messaging:
  url: "nats://nats.example.com:4222"
  # The chart always creates the JetStream streams. They are custom resources,
  # and this installs the NATS resource controller that reconciles them.
  controller:
    deploy: true

nack:
  jetstream:
    nats:
      # The controller needs the server address of its own.
      url: "nats://nats.example.com:4222"

objectStorage:
  endpoint: "https://s3.example.com"
  bucket: "tektona-sandboxes"
  existingSecret: "tektona-object-storage"

# See Configure Sign-On for a Microsoft Entra ID example.
auth:
  issuer: "https://auth.example.com"
  # Both URLs come from <issuer>/.well-known/openid-configuration.
  jwksUrl: "https://auth.example.com/.well-known/jwks.json"
  userinfoUrl: "https://auth.example.com/userinfo"
  existingSecret: "tektona-oidc"
  sessionSecret: "tektona-session"

credentials:
  masterSecret: "tektona-credentials"

sandboxGateway:
  certificate:
    create: true
values-runner.yaml
global:
  imageTag: "<commit sha>"
  imagePullSecrets:
    - name: "regcred"

location: "eu"

sandboxRunner:
  # kvm, or tcg for a node that cannot offer hardware virtualization.
  # There is no default: see Runner Node Requirements.
  acceleration: "kvm"

domains:
  sandboxIngress: "sandbox.example.com"
  agentlinkTrustDomain: "agentlink.example.com"

messaging:
  url: "nats://nats.example.com:4222"

objectStorage:
  endpoint: "https://s3.example.com"
  bucket: "tektona-sandboxes"
  existingSecret: "tektona-object-storage"

sandboxGateway:
  hostKeySecret: "tektona-access-gateway-host-key"

egress:
  certificate:
    # The inspection sidecar of the egress gateway needs this authority. Set
    # egress.injectCaSecret instead when you bring your own.
    create: true

agentGateway:
  certificate:
    # No chart creates this certificate unless you ask for it.
    create: true

location in the runner file must equal cluster.sandboxLocation in the control plane file. The API addresses runners by that token.

To install a backing service instead, set database.deploy, messaging.deploy or objectStorage.deploy. Read the chart README first. The installed database needs the CloudNativePG operator, installed separately, the JetStream streams need the NATS resource controller, and the installed object storage needs a second secret with its own server credentials.

No chart creates the S3 bucket. Create objectStorage.bucket yourself, before the first sandbox.

The database connection takes a password, a client certificate, or a rotating token from a managed identity. The chart README covers all three.

Render the charts

helm template tektona-control-plane ./deploy/charts/tektona-control-plane \
  --namespace tektona-system --values values-control-plane.yaml > /dev/null
helm template tektona-runner ./deploy/charts/tektona-runner \
  --namespace tektona-system --values values-runner.yaml > /dev/null

A missing host name or a missing secret name fails here, and the message names the value:

Error: execution error at (tektona-control-plane/templates/platform-api-deployment.yaml:136:24): domains.vnc is required

Do not gate on helm lint

helm lint reports a missing required value as [INFO] Missing required value and still exits 0. A chart that cannot install passes it. Use helm template for this check. Run helm lint --strict as well, for the checks it does make, but never as the gate.

Render the manifests and read them

helm template tektona-control-plane ./deploy/charts/tektona-control-plane \
  --namespace tektona-system \
  --values values-control-plane.yaml > rendered-control-plane.yaml

Keep the file. The two checks below read it, and it is also the document that your change process reviews.

First check the images. Every reference must resolve from your cluster. Mirror them into your own registry when the nodes have no route to the public one, then set global.imageRegistry.

grep -oE 'image: "[^"]+"' rendered-control-plane.yaml | sort -u

Then check the secret references:

{ grep -A1 'secretKeyRef:' rendered-control-plane.yaml | grep -oE 'name: [A-Za-z0-9._-]+'
  grep -oE 'secretName: [A-Za-z0-9._-]+' rendered-control-plane.yaml
} | awk '{print $2}' | sort -u > required-secrets.txt

Repeat all three commands for the runner chart, into files of its own:

helm template tektona-runner ./deploy/charts/tektona-runner \
  --namespace tektona-system \
  --values values-runner.yaml > rendered-runner.yaml

The image command misses the images of the bundled dependencies, which are not quoted in the output. Add grep -oE '^\s*image: [^"].*' to see those.

Confirm that the secrets exist

Every name in required-secrets.txt must exist in the namespace before the pods start.

while read -r name; do
  kubectl get secret "$name" --namespace tektona-system > /dev/null 2>&1 \
    || echo "missing: $name"
done < required-secrets.txt

cert-manager writes some of these names during the install. Expect them to be missing now:

  • sandbox-gateway-tls and the gateway authority secret beside it, when sandboxGateway.certificate.create is true. The runner list holds the same sandbox-gateway-tls name, which the control plane release creates.
  • egress-inject-ca, when egress.certificate.create is true in the runner chart.
  • hatchet-engine-tls, when hatchet.certificate.create is true.

The control plane writes one more name itself, rather than cert-manager. hatchet-client-config holds the token that the API uses to reach the build engine. The bootstrap job mints it after the install.

Every other name is yours to create. Two are easy to miss, and no chart creates either. agent-gateway-tls in the runner list carries the server certificate of the agent gateway. hatchet-config carries the keys of the build engine, and the control plane chart README has the command that makes it.

A pod that starts without its secret stays in CreateContainerConfigError. It recovers on its own once the secret appears.

Dry run against the cluster

helm install tektona-control-plane ./deploy/charts/tektona-control-plane \
  --namespace tektona-system --create-namespace \
  --values values-control-plane.yaml \
  --dry-run=server

A server side dry run checks the API versions of your cluster and the admission rules of your cluster. It catches a policy that rejects a privileged container, which the template render cannot see.

Custom resources need their definitions first

The dry run cannot validate a resource whose definition is not installed yet. This affects the CloudNativePG Cluster, the JetStream Stream and KeyValue objects, and the cert-manager Certificate objects. Install those operators first, or repeat the dry run after the operators run.

Check the runner nodes

The runner pods only schedule on labelled nodes:

kubectl label node <node> tektona.ai/sandbox-runner=true
kubectl get nodes --selector tektona.ai/sandbox-runner=true

Then check the devices on each of those nodes:

lsmod | grep vhost_vsock
ls -l /dev/kvm /dev/net/tun
  • vhost_vsock carries the channel to the guest agent. A node without it fails every sandbox session.
  • /dev/net/tun carries the sandbox network.
  • /dev/kvm gives hardware virtualization. The runner refuses to start without it when sandboxRunner.acceleration is kvm.

Runner Node Requirements covers the kernel modules, the settings and the speed each choice costs.

Install and watch the rollout

Install the control plane first. It issues the gateway certificate that the runner mounts.

helm install tektona-control-plane ./deploy/charts/tektona-control-plane \
  --namespace tektona-system --create-namespace \
  --values values-control-plane.yaml --wait --timeout 10m

helm install tektona-runner ./deploy/charts/tektona-runner \
  --namespace tektona-system \
  --values values-runner.yaml --wait --timeout 10m

--wait holds the command until every pod is ready. A failure leaves the release in place, so you can read the events:

kubectl get events --namespace tektona-system --sort-by .lastTimestamp | tail -20

Confirm the release

Check the pods of both releases:

kubectl get pods --namespace tektona-system \
  --selector app.kubernetes.io/instance=tektona-control-plane
kubectl get pods --namespace tektona-system \
  --selector app.kubernetes.io/instance=tektona-runner

Ask the API whether its dependencies answer. /healthz returns 200 whatever the state of the database, so use /readyz on the admin port:

kubectl port-forward --namespace tektona-system \
  service/tektona-control-plane-platform-api 9090:9090 &
sleep 2
curl --silent --show-error --fail http://localhost:9090/readyz && echo " ready"
kill %1

Every pod Ready is not enough. A control plane whose build engine refuses its worker token reports every pod Ready and serves an empty template catalog, so the first sandbox create has no template to name. Check the engine and the catalog before you go further:

kubectl port-forward --namespace tektona-system \
  service/tektona-control-plane-platform-api 9090:9090 &
sleep 2
# 1 means a registered worker holds a session to the engine.
curl --silent http://localhost:9090/metrics \
  | grep tektona_template_build_engine_connected
kill %1

The gauge reads 1 within a minute of the pods settling. A 0 that does not move, with invalid auth token in the API log, is the stale worker token in When a check fails. Waiting longer never clears it.

Then the catalog, which the API reconciles once the engine answers:

tektona template ls

It lists tektona/ubuntu and tektona/desktop. An empty list after eleven minutes means the reconcile gave up; restart platform-api rather than wait.

Then prove the whole path. Route your API host name and your console host name to the two services, sign in, and create a sandbox:

tektona login
tektona sandbox create tektona/desktop
tektona sandbox ls
tektona sandbox ssh <sandbox-id>

A sandbox that reaches the running state proves the control plane, the messaging, the object storage and one runner node together. Delete it when you are done:

tektona sandbox rm <sandbox-id>

When a check fails

SymptomCauseFix
helm template fails and names a valueThe values file misses a host name or a secret nameSet the value that the message names
A pod stays in CreateContainerConfigErrorA secret it reads as an environment variable does not existCreate the secret. The pod recovers on its own
A pod stays in ContainerCreating with a FailedMount eventA secret it mounts as a volume does not existCreate the secret. Most of the runner secrets are volumes
The API pod is ready but every call fails/healthz does not test the dependenciesCheck /readyz on the admin port, then the database and NATS endpoints
Every pod reports ImagePullBackOffThe registry needs credentials, or the tag does not existSet global.imagePullSecrets, and global.imageTag when you install from the repository
tektona-runner-sandbox-runner stays in ContainerCreatingThe node has no /dev/vhost-vsockLoad the vhost_vsock module on that node
A sandbox create call hangsThe JetStream streams do not existThe NATS resource controller is not running. Set messaging.controller.deploy, or install it yourself
The API pod restarts and logs open KV bucket and bucket not foundThe JetStream key value buckets do not existThe same. The chart creates both; the controller reconciles them
A sandbox create call fails on storageThe S3 bucket does not existCreate the bucket that objectStorage.bucket names
Every pod is Ready, the template list is empty, and the API logs invalid auth tokenThe Hatchet database was re-created and the worker token still names the old oneDelete the hatchet-client-config secret, helm upgrade, then restart platform-api
The runner DaemonSet has no podsNo node carries the runner labelLabel the nodes that run sandboxes
A sandbox starts, but everything inside it is slowThe node runs tcg, or is itself a virtual machineSee Runner Node Requirements
sandbox-runner exits with a KVM device erroracceleration is kvm and the node has no /dev/kvmFix the node, or set tcg and accept the loss
A preview URL does not answerNo route reaches the sandbox ingress domainPoint the wildcard domain at the access gateway

Routes

Set ingress.kind to HTTPRoute or to Ingress and the control plane chart creates the routes for the API and the console. The default is none. HTTPRoute needs the Gateway API definitions on the cluster, and the chart refuses to render without them. helm template reads no cluster, so add --api-versions gateway.networking.k8s.io/v1 there. The sandbox preview domain always needs a route of your own:

HostServicePort
domains.apitektona-control-plane-platform-api8080
domains.apptektona-control-plane-platform-frontend80
domains.sandboxIngress (wildcard)tektona-runner-access-gateway8445

On this page