Custom resources
Kuben’s custom resources are in the kuben.dev/v1alpha1 API group. The manifests ship in the Helm chart (charts/kuben/crds/kuben.dev_all.yaml); the binary embeds the same file and server-side-applies it at every start. Validation is done with the schemas and CEL rules inside the CRDs, so there is no admission webhook to run.
Who writes them
Section titled “Who writes them”Kuben’s database is the source of desired state (ADR-032). The console, the API and the CLI write to the database; the materializer writes the Project, Environment and App objects from it with server-side apply (field manager kuben-materializer), or, for an app the cluster’s agent delivers, an ApplicationRuntime. The controllers turn those objects into Deployments, Services, CronJobs, volumes and HTTPRoutes.
Read them with kubectl as much as you like. A change made with kubectl or a GitOps tool to an object the materializer wrote is drift: it is recorded on the app, and the object is written again from the database. Change apps through the console, the API or the CLI. KubenConfig is the exception: it belongs to the operator, and is written by the Helm chart from platform.*, by kuben setup, or by hand.
| Kind | Short name | Scope | Written by | Purpose |
|---|---|---|---|---|
KubenConfig |
kcfg |
cluster | the operator | platform-wide settings (the singleton kuben) |
Project |
kproj |
cluster | the materializer | a group of environments |
Environment |
kenv |
cluster | the materializer | one namespace with quota, network policy and protection |
App |
kapp |
namespaced (the environment’s) | the materializer | a deployable unit, reconciled by the App controller |
BuildRun |
kbr |
namespaced (the build namespace) | the build worker | one build attempt; owns its Job and source Secret |
Release |
krel |
namespaced | nobody | kept in the schema for compatibility; releases are rows in the database |
ApplicationRuntime |
kbrt |
namespaced | the hub, over AgentLink | the execution envelope an agent applies (internal) |
ExecutionTask |
kbet |
namespaced | nobody yet | reserved for tasks an agent runs (internal) |
KubenConfig
Section titled “KubenConfig”apiVersion: kuben.dev/v1alpha1kind: KubenConfigmetadata: name: kubenspec: baseDomain: apps.example.com gatewayClassName: traefik # Kuben creates and owns kuben-system/kuben gateway: kuben-system/kuben # or a Gateway you dedicate (label kuben.dev/gateway-owner=kuben) gatewayPorts: { http: 8000, https: 8443 } # default 80 and 443; Traefik matches its entry points clusterIssuer: letsencrypt wildcardTlsSecret: apps-wildcard # optional: one listener for *.<baseDomain> registry: # optional prefix: ghcr.io/acme credentialsSecret: registry-credentials sizes: # compute presets; these are the defaults - { name: nano, cpuRequest: 50m, memoryRequest: 64Mi, memoryLimit: 128Mi } - { name: small, cpuRequest: 100m, memoryRequest: 128Mi, memoryLimit: 256Mi } - { name: medium, cpuRequest: 250m, memoryRequest: 512Mi, memoryLimit: 1Gi } - { name: large, cpuRequest: 1, memoryRequest: 2Gi, memoryLimit: 4Gi }Size presets have no CPU limit by default (cpuLimit is optional), which avoids throttling latency-sensitive apps. Add your own presets or change the defaults; apps reference them by name. The status has a Gateway condition that says whether Kuben can use its Gateway; see Exposing apps and HTTPS.
The Helm chart sets baseDomain, gateway, gatewayClassName, gatewayPorts and clusterIssuer; set the other fields on the resource itself.
Project
Section titled “Project”apiVersion: kuben.dev/v1alpha1kind: Projectmetadata: name: shopspec: displayName: Online Shop description: Storefront and its services previews: max: 10 # the defaultstatus: environments: 2Environment
Section titled “Environment”Named <project>-<environment>. It owns one namespace, kb-<project>-<environment>, with Pod Security Admission (baseline enforced, restricted warned), a NetworkPolicy and, with quota, a ResourceQuota.
apiVersion: kuben.dev/v1alpha1kind: Environmentmetadata: name: shop-productionspec: project: shop type: production # standard | production | preview deletionPolicy: Delete # Retain | Delete; the materializer writes Delete protection: requireApprovals: 0 deletionGrace: 168h # soft-delete grace period (production environments) quota: cpu: "8" memory: 16Gi pods: 50status: namespace: kb-shop-production phase: Ready # Pending | Ready | Terminating | Degradedtype: production marks the environment as protected: deleting it needs the env-delete-protected permission (owners), and the namespace goes only after the grace period. type: preview environments are the preview environments of pull requests; ttl (idle, max) is part of the schema.
apiVersion: kuben.dev/v1alpha1kind: Appmetadata: name: api namespace: kb-shop-productionspec: source: image: ghcr.io/acme/shop-api@sha256:… # or git: { repo, branch: main, path, build: { strategy: auto, dockerfile } } runtime: processes: web: command: [] # image default port: 8080 size: small replicas: { min: 2, max: 4 } # max > min enables CPU autoscaling protocol: http # http | tcp (cluster-internal only) nightly: command: ["report", "--yesterday"] schedule: "0 3 * * *" # runs as a CronJob; no port timeZone: Europe/Berlin healthCheck: path: /healthz port: 8080 fsGroup: 1000 # for non-root images writing to volumes env: - name: LOG_LEVEL value: info - name: DATABASE_URL fromSecret: { name: db-credentials, key: url } imagePullSecrets: [registry-login] domains: - host: api.shop.example.com tls: auto # auto | none | the name of a Secret volumes: - name: data mountPath: /data size: 5Gi storageClass: "" # cluster defaultstatus: url: https://api.shop.example.com conditions: - type: Ready status: "True" reason: Available - type: Exposed status: "True" reason: RouteAppliedNotes:
sourcenames exactly one ofimageorgit(otherwiseReadyis false withInvalidSource). Agitsource runs once a build has produced an image (AwaitingBuilduntil then).git.build.strategyisdockerfile,railpackorauto(the default).- An environment variable takes one of
value,fromSecretorfromService. - At most one process may have a
port, and a process withschedulemay not have one. - An app with
volumesruns one process with at most one replica, and rolls withRecreate. idle(mode: off | zero | throttle,after; defaultsoffand15m) is reserved for scale-to-zero and has no effect yet.- An App with the annotation
kuben.dev/handoverhas been handed to the cluster’s agent; the App controller leaves it alone.
The Ready and Exposed reasons are listed under Troubleshooting.
BuildRun
Section titled “BuildRun”One per build attempt, named kbuild-<attempt> in the build namespace (build.namespace). It owns the attempt’s Job and the Secret with its repository-scoped fetch token, so deleting it removes both. The build worker creates it and follows the Job; the attempt’s state and history are in the database, behind GET …/apps/{app}/builds.
apiVersion: kuben.dev/v1alpha1kind: BuildRunmetadata: name: kbuild-0190f3c2-…spec: app: 0190f3a8-… # the application target's id repo: https://github.com/acme/shop-api gitRef: 3f9c1e… # the commit path: "" # the build context in the repository strategy: auto # dockerfile | railpack | auto image: ghcr.io/acme/shop-api # where the image is pushedRelease
Section titled “Release”The schema is kept so existing manifests and tools keep validating. Kuben does not create Release objects: releases (the image digest, the configuration revision and the secret revisions an app runs with) are rows in the database, and each deployment run renders from them. See Rollbacks.
ApplicationRuntime and ExecutionTask
Section titled “ApplicationRuntime and ExecutionTask”Internal resources of the cluster agent (ADR-027). The hub writes one ApplicationRuntime per application target that an agent delivers; it holds the frozen render plan with its generation and input hash, and the agent reports what it applied (effectiveGeneration, inventory) in the status. CEL rules keep the envelope ordered: generation and controlEpoch never decrease, and the same generation always carries the same plan. ExecutionTask (builds, backups, restores, migrations and platform changes run by an agent) is defined but not written by this version. Do not edit either by hand.