Helm chart values
The chart is published as an OCI artifact:
helm show values oci://ghcr.io/teamtem-dev/charts/kuben| Value | Default | |
|---|---|---|
image.repository |
ghcr.io/teamtem-dev/kuben |
multi-arch (amd64, arm64), distroless, non-root |
image.tag |
the chart’s appVersion |
|
image.pullPolicy |
IfNotPresent |
|
imagePullSecrets |
[] |
Replicas and database
Section titled “Replicas and database”| Value | Default | |
|---|---|---|
replicaCount |
1 |
every replica serves the API; only the holder of the kuben-controller Lease runs the controllers. For several replicas or high availability use an external PostgreSQL: the chart’s own is a single instance |
database.url |
"" |
an external PostgreSQL, e.g. postgres://kuben:…@db.example.com/kuben |
database.existingSecret |
"" |
Secret with a url key (PostgreSQL credentials); wins over database.url |
With database.url and database.existingSecret both empty, the chart runs its own PostgreSQL; with either set, it renders none. See High availability.
The chart’s own PostgreSQL
Section titled “The chart’s own PostgreSQL”| Value | Default | |
|---|---|---|
postgresql.image.repository |
docker.io/library/postgres |
|
postgresql.image.tag |
"17-alpine" |
|
postgresql.image.digest |
the release’s pin | from the bundle lock (kuben version --bundle); empty pulls the tag |
postgresql.image.pullPolicy |
IfNotPresent |
|
postgresql.persistence.size |
8Gi |
|
postgresql.persistence.storageClass |
"" |
the cluster default; it must offer ReadWriteOnce volumes |
postgresql.resources |
requests cpu: 50m, memory: 128Mi; limits memory: 512Mi |
It is a single-instance StatefulSet <fullname>-postgresql with the volume data-<fullname>-postgresql-0; for a release named kuben the fullname is kuben. Kuben connects as the ordinary role kuben, which owns the database kuben, so row-level security applies to it. The superuser postgres is for maintenance only.
The passwords are in the Secret <fullname>-postgresql: password for the role kuben, postgres-password for postgres. helm uninstall keeps that Secret and the volume, and a re-install finds the data with the same password. To start over, delete both:
kubectl -n kuben-system delete pvc/data-kuben-postgresql-0 secret/kuben-postgresqlSecret keyring
Section titled “Secret keyring”| Value | Default | |
|---|---|---|
secrets.existingKeyringSecret |
"" |
Secret with a secrets.keyring key (version:base64-key lines). Empty: the chart makes <fullname>-secrets-keyring with one random key and keeps it on helm uninstall |
Managed secret values are sealed with the keys of this keyring, which never enters the database. Every replica and the backup job mount the same Secret. Back it up apart from the database: a restore needs it. See Backup and restore.
Backups
Section titled “Backups”| Value | Default | |
|---|---|---|
backup.enabled |
true |
a CronJob <fullname>-backup that runs kuben backup |
backup.schedule |
"17 3 * * *" |
cron schedule |
backup.keep |
7 |
backups kept on the volume |
backup.maxAgeHours |
26 |
Kuben reports itself degraded when the newest good backup is older (0 when backups are off) |
backup.includeKeyring |
false |
also copy the keyring into each backup; whoever has such a backup can read every secret |
backup.beforeUpgrade |
true |
on helm upgrade, a hook Job takes a backup and runs the new version’s kuben upgrade-check; a failure stops the upgrade before anything changed |
backup.upgradeCheckMajor |
false |
pass --major to that kuben upgrade-check: set true for the one upgrade across a major version (1.2 → 2.0), after reading its release notes |
backup.image |
"" |
an image with PostgreSQL client tools at least as new as the server; empty: the chart’s PostgreSQL image |
backup.deadlineSeconds |
3600 |
deadline of a backup Job |
backup.persistence.size |
20Gi |
the backup volume <fullname>-backup, kept on helm uninstall |
backup.persistence.storageClass |
"" |
the cluster default |
backup.persistence.existingClaim |
"" |
write to this claim instead of making one |
backup.resources |
requests cpu: 50m, memory: 64Mi; limits memory: 512Mi |
The backup Job copies the kuben binary out of Kuben’s image and runs it next to PostgreSQL’s client tools, without a service-account token. The pre-upgrade hook runs only when the backup volume exists. Copy backups off the cluster: a backup on the same storage is no backup.
Upgrading from 1.0 and 1.1
Section titled “Upgrading from 1.0 and 1.1”| Value | Default | |
|---|---|---|
legacy.sqliteVolumeAcknowledged |
false |
true: upgrade over the SQLite volume of 1.0 or 1.1 and start with a new, empty database |
Kuben 1.0 and 1.1 kept their data in SQLite on the volume <fullname>-data, which this chart does not read. The persistence.* values that created it are gone. An upgrade of a release that still has that volume stops with a message, so that a new, empty database is never taken for lost data. With legacy.sqliteVolumeAcknowledged=true it goes ahead; the old volume stays untouched.
Admin and security
Section titled “Admin and security”| Value | Default | |
|---|---|---|
admin.email |
admin@kuben.local |
initial admin |
admin.existingSecret |
"" |
Secret with a password key. Empty: generated on first start into kuben-initial-admin, never logged |
publicUrl |
"" |
e.g. https://kuben.example.com |
security.cookieSecure |
true |
keep true in production; auto follows publicUrl |
The chart always sets KUBEN_SECURITY__TRUST_FORWARDED_FOR=true (Kuben sits behind the Gateway), KUBEN_KUBE__REQUIRED=true and KUBEN_KUBE__LEADER_ELECTION=true.
Platform (KubenConfig)
Section titled “Platform (KubenConfig)”| Value | Default | |
|---|---|---|
platform.create |
true |
write the KubenConfig singleton |
platform.baseDomain |
"" |
apps get <app>-<environment>.<baseDomain> |
platform.gatewayClassName |
"" |
GatewayClass of the Gateway Kuben creates and owns (kuben-system/kuben unless platform.gateway is set) |
platform.gateway |
"" |
Gateway API Gateway for app routes, as namespace/name. Without gatewayClassName it must exist and carry the label kuben.dev/gateway-owner=kuben |
platform.gatewayPorts |
{} |
listener ports of Kuben’s Gateway, {http, https}; default 80 and 443. Traefik needs {http: 8000, https: 8443} |
platform.clusterIssuer |
"" |
cert-manager ClusterIssuer; enables HTTPS once it is Ready |
The chart sets only these fields of KubenConfig. The others (wildcardTlsSecret, registry, sizes) are set on the resource itself, for example kubectl patch kubenconfig kuben --type merge -p '{"spec":{"wildcardTlsSecret":"apps-wildcard"}}'; see Custom resources.
Single sign-on
Section titled “Single sign-on”| Value | Default | |
|---|---|---|
sso.enabled |
false |
sign-in with an OpenID Connect provider; needs publicUrl, sso.issuer, sso.clientId and sso.existingSecret |
sso.issuer |
"" |
the provider’s issuer URL |
sso.clientId |
"" |
|
sso.existingSecret |
"" |
Secret with the client secret, mounted as a file (KUBEN_SSO__CLIENT_SECRET_FILE) |
sso.clientSecretKey |
client-secret |
its key in that Secret |
sso.displayName |
Single sign-on |
the sign-in button |
sso.scopes |
[openid, email, profile] |
|
sso.groupClaim |
groups |
the ID token claim with the person’s groups |
sso.groups |
{} |
provider group → role (viewer, developer, admin, owner), applied at every sign-in |
sso.defaultRole |
"" |
role of people in no mapped group; empty refuses them |
sso.allowedDomains |
[] |
email domains allowed; empty allows any |
sso.requireVerifiedEmail |
true |
|
sso.org |
"" |
organization people join; empty: the bootstrap organization |
sso.disablePasswordForLinked |
false |
linked accounts cannot also sign in with a password |
Register <publicUrl>/api/v1/auth/sso/callback at the provider. See Sign-in and security.
CI trust
Section titled “CI trust”| Value | Default | |
|---|---|---|
ci.githubActions.enabled |
false |
GitHub Actions may exchange its OIDC token at <publicUrl>/api/v1/ci/github/token for a short-lived Kuben token under a trust policy (Settings → CI trust policies) |
ci.githubActions.audience |
"" |
the audience workflows request; empty: publicUrl |
See Deploy from CI.
Git sources and builds
Section titled “Git sources and builds”| Value | Default | |
|---|---|---|
git.github.appId |
"" |
the GitHub App’s numeric id; empty turns Git sources off |
git.github.existingSecret |
"" |
Secret with the App’s private key (file KUBEN_GIT__GITHUB_PRIVATE_KEY_FILE) and webhook secret |
git.github.privateKeyKey |
private-key.pem |
key of the PEM private key in that Secret |
git.github.webhookSecretKey |
webhook-secret |
key of the webhook secret in that Secret |
git.github.apiUrl / git.github.cloneUrl |
https://api.github.com / https://github.com |
for GitHub Enterprise Server |
build.enabled |
false |
run the build worker; sources come from git.github, from the organizations’ Git connections, or both |
build.namespace |
"" |
namespace of build Jobs; empty: the release namespace |
build.buildkitImage, build.fetchImage, build.scannerImage |
"" |
empty: the server’s defaults; pin by digest in production |
build.railpackFrontend, build.railpackImage |
"" |
both set: Railpack builds for sources without a Dockerfile |
build.maxConcurrent / build.maxConcurrentPerOrg |
2 / 1 |
builds at once |
build.pushSecret |
"" |
a kubernetes.io/dockerconfigjson Secret in the build namespace with push access |
build.insecureRegistry |
false |
push over plain HTTP |
build.nodePool |
"" |
node selector key=value of a build pool |
build.cpuRequest, build.cpuLimit, build.memory, build.ephemeralStorage, build.deadlineSeconds |
empty / 0 |
per build; empty keeps 500m, 2, 2Gi, 10Gi and 30 minutes |
build.logTailKiB |
0 |
how much of the end of each build’s log is kept after the build, 1 to 1024 KiB; 0 keeps the server’s 256 |
integrations.allowPrivateHosts |
false |
let Git connections and organization registries reach private, loopback and link-local addresses (a GitLab, Gitea or registry inside your network); every organization admin can then reach them through Kuben |
GitHub delivers the App’s webhooks to <publicUrl>/api/v1/webhooks/github.
Token connections to GitHub, GitLab and Gitea/Forgejo, and organization registry logins, are not chart values: organization admins add them at runtime under Settings → Integrations and Settings → Registries, and their secrets are sealed with the keyring (see Git providers and Container registries). Their push webhooks go to:
| Provider | Webhook URL | Verified with |
|---|---|---|
| GitHub App | <publicUrl>/api/v1/webhooks/github |
the App’s webhook secret |
| GitLab | <publicUrl>/api/v1/webhooks/gitlab/<connection id> |
X-Gitlab-Token |
| Gitea / Forgejo | <publicUrl>/api/v1/webhooks/gitea/<connection id> |
X-Gitea-Signature (HMAC-SHA256) |
Set publicUrl and make it reachable from the Git host.
Every value is an environment variable of the Kuben pod (KUBEN_SSO__*, KUBEN_CI__*, KUBEN_GIT__*, KUBEN_BUILD__*, KUBEN_INTEGRATIONS__*, see Configuration); secrets come from the Secrets you name and are never written into the pod spec. A value that needs another (for example sso.enabled without sso.issuer) stops helm install with a message.
Cluster agent
Section titled “Cluster agent”| Value | Default | |
|---|---|---|
agent.enabled |
true |
run the cluster agent (ADR-027): a separate, unprivileged pod that carries apps out. It enrolls on its own from the Secret kuben-agent-enrollment Kuben keeps (a short-lived token until it has a certificate) and keeps its identity in kuben-agent-identity. Apps made while it is linked are delivered by it; earlier ones stay with the App controller until handed over |
agent.port |
7443 |
the port Kuben listens on for agents (service port agentlink) |
The agent requests 10m CPU and 24 MiB of memory (limit 96 MiB). Kuben keeps the CA agents pin in the Secret kuben-agentlink-ca. Run a single Kuben replica with the agent until high availability lands.
Console route
Section titled “Console route”| Value | Default | |
|---|---|---|
route.enabled |
false |
expose the console through an HTTPRoute |
route.hostname |
"" |
|
route.gateway.name / route.gateway.namespace |
"" |
Service, metrics, resources
Section titled “Service, metrics, resources”| Value | Default | |
|---|---|---|
service.type |
ClusterIP |
|
service.port |
80 |
|
metrics.port |
9090 |
|
logLevel |
info |
|
resources.requests |
cpu: 50m, memory: 64Mi |
|
resources.limits |
memory: 256Mi |
no CPU limit |
podAnnotations, nodeSelector, tolerations, affinity |
{} / [] |
What the chart renders
Section titled “What the chart renders”- A
DeploymentwithRollingUpdate(maxUnavailable: 0,maxSurge: 1), and aPodDisruptionBudgetfor several replicas. - Without an external database: the PostgreSQL
StatefulSet, itsServiceand its Secret, and an init container in the Kuben pod that waits for PostgreSQL to answer. - A security context:
runAsNonRoot, UID 65532 (numeric, so Kubernetes can verify it), read-only root filesystem, all capabilities dropped,RuntimeDefaultseccomp. - Liveness and readiness probes on
/livezand/readyz. - A
ServiceAccount, aClusterRolefor the tenant resources it manages and a namespacedRolefor the controller Lease. The full list is intemplates/rbac.yaml. - The CRDs in
crds/. Helm installs them once; the binary server-side-applies them again on every start so upgrades pick up new fields. - The
KubenConfigsingleton fromplatform.*, kept onhelm uninstall. - The keyring Secret (unless
secrets.existingKeyringSecret), kept onhelm uninstall. - With
backup.enabled: the backup volume (kept onhelm uninstall), the CronJob, and on upgrades the pre-upgrade hook Job. - With
agent.enabled: the cluster agent’s Deployment, its ServiceAccount and RBAC (files/agent.yaml, the same manifestkuben setupapplies).
The chart is linted with helm lint --strict, rendered and scanned by Trivy for misconfigurations on every pull request; accepted findings are justified in .trivyignore.yaml.