Skip to content

Helm chart values

The Cornus Helm chart (deploy/helm/cornus, also published as an OCI artifact) deploys the server in-cluster as a StatefulSet + PVC + Service + RBAC, preset to the kubernetes deploy backend. This page documents every value in values.yaml; see Installation for the install walkthrough.

Chart version 0.3.0, app version 0.1.0.

Installing

sh
# From the OCI registry (recommended):
helm install cornus oci://ghcr.io/moriyoshi/charts/cornus

# From a checked-out chart, overriding values:
helm install cornus deploy/helm/cornus \
  --set storage='s3://my-bucket?region=us-east-1' \
  --set tls.enabled=true

Override values with --set key=value or a -f my-values.yaml file.

Image

ValueDefaultDescription
image.repositoryghcr.io/moriyoshi/cornusServer image. Override for a locally built or mirrored image.
image.tag""Image tag. Empty defaults to the chart appVersion.
image.pullPolicyIfNotPresentStandard Kubernetes image pull policy.

Server

ValueDefaultDescription
addr":5000"Listen address inside the container.
replicas1Server replica count. 1 keeps single-replica behavior; > 1 switches on multi-replica mode (see Multi-replica mode for its requirements).
deployBackendkubernetesBackend for workloads created through /.cornus/v1/deploy (sets CORNUS_DEPLOY_BACKEND). The chart runs in-cluster and deploys into its own namespace. Set dockerhost only if the pod has a Docker socket mounted.
resources{}Pod resource requests/limits, rendered verbatim.

Storage and garbage collection

ValueDefaultDescription
storage""Registry persistence backend (CORNUS_STORAGE): a path, file://, mem://, or s3://bucket?region=.... Empty keeps the CAS on the data-dir PVC. Must be an s3:// URL when replicas > 1. See storage backends.
persistence.size20GiSize of the per-replica data-dir PVC.
persistence.storageClassNameunsetPVC storage class (commented out by default; cluster default is used).
gc.interval""CORNUS_GC_INTERVAL: a Go duration (e.g. 24h). When set, each replica runs the storage mark-and-sweep GC on this period. Empty means GC only runs on demand via POST /.cornus/v1/gc.
gc.lease""CORNUS_GC_LEASE: opt-in cross-replica GC coordination (requires gc.interval). kube elects a single sweeper per tick via a coordination.k8s.io Lease; kube:<name> or kube:<namespace>/<name> override the Lease identity. Makes gc.interval safe with replicas > 1.

Service and registry exposure

registry.exposure selects how workload images are exposed for cluster nodes to pull — the server advertises a node-reachable registry host via GET /.cornus/v1/info, and the chart wires the matching topology.

ValueDefaultDescription
service.type""Service type. Empty derives it from registry.exposure (NodePort for nodePort, else ClusterIP). Set only to override.
service.port5000Service port.
registry.exposurenodePortTopology the server advertises: nodePort, clusterIP, hostPort, hostNetwork, or ingress (see table below).
registry.nodePort30500Fixed NodePort for nodePort exposure (lets each node's containerd registry config be pre-provisioned). Empty lets Kubernetes allocate one.
registry.hostPort5000Node port the registry binds for hostPort exposure.
registry.advertiseHost""Overrides the registry host baked into deploy pull refs (CORNUS_ADVERTISE_REGISTRY). Required for clusterIP / hostPort / hostNetwork / ingress. Prefix with https:// for a TLS registry.
registry.nodeCIDR""For nodePort / clusterIP, emits a NetworkPolicy allowing nodes in this CIDR to reach the registry port — required under a default-deny posture.

registry.exposure values

ValueHow nodes pullRequires
nodePort (default)localhost:<nodePort> on each nodeNothing extra; auto-advertised from the Service.
clusterIPThe Service ClusterIPadvertiseHost; nodeCIDR allow under default-deny; node trust for the ClusterIP.
hostPort<nodeIP>:<port> (CNI portmap)advertiseHost; pin the pod with nodeSelector. NetworkPolicy-immune.
hostNetworkHost netns listenerA privileged PodSecurity namespace; advertiseHost; pin the pod. NetworkPolicy-immune.
ingressAn ingress host/VIPadvertiseHost; nodes resolve the host and trust its cert (real DNS + TLS).

Ingress defaults

Server-side fallbacks for workloads that opt into ingress (deploy spec ingress: / Compose x-cornus-ingress:). Leave every field empty (the default) to require each workload to specify its own host, so nothing is auto-exposed.

ValueDefaultDescription
ingress.domain""CORNUS_INGRESS_DOMAIN: base wildcard domain for host auto-derivation (e.g. preview.example.com). Empty means a workload must set its own host or domain.
ingress.className""CORNUS_INGRESS_CLASS: default IngressClassName. Empty uses the cluster default.
ingress.tlsIssuer""CORNUS_INGRESS_TLS_ISSUER: default cert-manager cluster-issuer for TLS-enabled ingresses. Empty means a TLS-requesting workload must supply its own secret/issuer.
ingress.enforceDomainfalseCORNUS_INGRESS_ENFORCE_DOMAIN: when true (and domain is set), reject a workload whose resolved host falls outside domain, so a shared controller cannot be made to serve an arbitrary hostname.

Privilege

ValueDefaultDescription
privilegedtrueThe in-process build engine needs runc + overlayfs; privileged is the simplest posture. Set false and provide the rootless prerequisites for hardened clusters. See Privilege posture.

TLS

Opt-in HTTPS. When enabled, the server serves from a mounted Secret (tls.crt / tls.key, plus ca.crt for mTLS) and hot-reloads the cert on file change.

ValueDefaultDescription
tls.enabledfalseServe HTTPS.
tls.secretNamecornus-tlsSecret mounted at /etc/cornus/tls. Produced by cert-manager when tls.certManager.enabled, else provide an existing one with the same keys.
tls.clientCAfalseVerify client certs (mTLS) using ca.crt from the Secret. A verified cert's CommonName becomes the caller identity (see CORNUS_API_POLICY in Security and authentication).
tls.certManager.enabledfalseRender a cert-manager Certificate that writes secretName and is auto-rotated. Requires cert-manager and an Issuer/ClusterIssuer.
tls.certManager.issuerRef.name""Issuer/ClusterIssuer name.
tls.certManager.issuerRef.kindClusterIssuerClusterIssuer or Issuer.
tls.certManager.dnsNames[]DNS names for the cert; defaults to the in-cluster Service name when empty.
tls.certManager.duration2160hCertificate lifetime (90d).
tls.certManager.renewBefore720hRenew-before window (30d); cornus hot-reloads the new cert.

Auth (JWT and SSH keys)

Opt-in JWT verification for the server API (the kube-auth turnkey path). Each set value renders the matching CORNUS_JWT_* env; leaving all empty renders nothing (auth stays off unless configured elsewhere). See Security and authentication.

Independently, the chart always creates <release>-cornus-installation and passes its value as CORNUS_INSTALLATION_SECRET. This shared internal key lets authenticated builds and all deploy backends use the co-located registry; it does not turn client authentication on. Helm lookup preserves the installed value across upgrades and replicas. A bare helm template cannot query the cluster, so its rendered Secret contains a fresh random value on every render; that output difference is cosmetic and does not rotate an installed Secret.

SSH public keys are also opt-in. When declarative keys are set, the chart passes them as CORNUS_AUTHORIZED_KEYS. For multiple replicas it additionally sets CORNUS_AUTH_KEYSTORE=none, because each StatefulSet replica has a separate PVC and runtime enrollment could not update every replica consistently.

ValueDefaultDescription
auth.ssh.authorizedKeys""Newline-separated OpenSSH authorized_keys entries. Setting it enables SSH-public-key client authentication. In multi-replica mode the entries remain active but runtime enrollment is disabled.
auth.jwt.jwksURL""HTTPS URL of a JWKS document (CORNUS_JWT_JWKS_URL), e.g. the cluster's ServiceAccount OIDC JWKS. Mutually exclusive with jwksConfigMap / jwksSecret.
auth.jwt.jwksConfigMap""Name of an existing ConfigMap holding a JWKS document to mount (CORNUS_JWT_JWKS_FILE). Set exactly one of jwksConfigMap / jwksSecret.
auth.jwt.jwksSecret""Name of an existing Secret holding a JWKS document to mount.
auth.jwt.jwksKeyjwks.jsonKey inside the ConfigMap/Secret holding the JWKS JSON. Mounted read-only at /etc/cornus/jwks.
auth.jwt.audience""Required aud claim (CORNUS_JWT_AUDIENCE). Tokens minted by cornus kube-auth must use the same audience.
auth.jwt.issuer""Optional expected iss claim (CORNUS_JWT_ISSUER). Unset skips the issuer check.

Caretaker TLS

ValueDefaultDescription
caretakerTlsSecret""Name of an existing Secret (CORNUS_CARETAKER_TLS_SECRET) whose material server-bound caretaker sidecars present when dialing the server. Keys follow the kubernetes.io/tls convention: ca.crt (added to system roots — use with a private-CA tls.enabled cert) and, optionally, tls.crt / tls.key (an mTLS client pair for tls.clientCA). Empty renders nothing.

Tailscale Funnel sidecar

Opt-in sidecar for the tailscale tunnel backend: a tailscaled container that joins the tailnet unattended via an authkey Secret, plus an initContainer that copies the tailscale CLI onto a volume shared with the cornus container, so no custom cornus image is needed. Runs in userspace networking mode (no NET_ADMIN, no TUN device). See the Tunnels guide for the full walkthrough.

ValueDefaultDescription
tailscale.enabledfalseEnable the sidecar. Sets CORNUS_TUNNEL_BACKEND=tailscale, CORNUS_TUNNEL_TAILSCALE_BIN, and TS_SOCKET on the cornus container.
tailscale.image.repository / tag / pullPolicyghcr.io/tailscale/tailscale / stable / IfNotPresentSidecar and initContainer image.
tailscale.authKeySecret""Required when enabled. Name of an existing Secret holding a tailnet auth key. Use a reusable, ideally ephemeral-tagged key — the sidecar's state directory is an emptyDir, not persisted across pod restarts.
tailscale.authKeySecretKeyauthkeyKey inside the Secret holding the auth key.
tailscale.hostname""TS_HOSTNAME: the tailnet device name, so the Funnel URL is stable across restarts. Empty derives it from the release fullname.
tailscale.extraArgs""Extra flags appended to the sidecar's unattended tailscale up (TS_EXTRA_ARGS), e.g. --accept-dns=false. The chart already supplies --authkey and --hostname.
tailscale.resources{}Sidecar container resources.

RBAC and scheduling

ValueDefaultDescription
rbac.createtrueGrant RBAC for the in-cluster kubernetes deploy backend, including Cornus-labelled registry pull Secrets, and, when replicas > 1, the kube-native hub store (HubEndpoint CRs, Leases, CRD self-install). The Lease verbs also cover gc.lease.
nodeSelector{}Standard pod nodeSelector.
tolerations[]Standard pod tolerations.
affinity{}Pod affinity, rendered verbatim when set. Empty with replicas > 1 renders a default soft pod anti-affinity that spreads replicas across nodes; setting this replaces that default.

Multi-replica mode

Setting replicas > 1 switches the workload-to-workload hub to its multi-replica mode: the chart sets CORNUS_HUB_STORE=kube, adds a headless Service for stable per-pod DNS, and points CORNUS_HUB_FORWARD_URL at it for cross-replica delivery. Requirements and caveats:

  • storage MUST be an s3:// URL (enforced at render time) — each replica gets its own PVC, so a PVC-backed CAS would be inconsistent across replicas behind one Service. The PVC then holds only the per-replica build cache.
  • The StatefulSet serviceName switches to the headless Service, and that field is immutable: moving an existing release between 1 and > 1 replicas requires deleting the StatefulSet first (PVCs are retained).
  • With tls.enabled, inter-replica forward dials use wss:// and verify the serving cert against the container trust store, so the cert must cover the per-pod names (*.<fullname>-hub.<namespace>.svc) and chain to a trusted root.
  • Garbage collection: gc.interval alone runs an uncoordinated sweep on every replica over the shared S3 CAS. Set gc.lease: kube together with gc.interval so the replicas elect a single sweeper per tick through a Lease.

See also

Released under the Apache-2.0 License.