Skip to content

Hub on Kubernetes

The towonel-node chart runs the hub, the edge, or both. For a hub-only Deployment set edge.enabled: false; edges run separately — see Edge on Kubernetes. To run both in one pod (single-node topology) leave the defaults and skip to Combined.

The chart is published to oci://codeberg.org/towonel/charts/towonel-node, ships a values.schema.json, and pins its image by digest per release. The hub / edge blocks derive the listeners, ports, probes and resource defaults from the enabled roles; app config (env, secrets, persistence) uses the chart’s native keys under controllers.main.

SQLite is the default — single replica, on-disk file under TOWONEL_DATA_DIR. Postgres is required to run multiple hub replicas.

DriverReplicasDSN
sqlite1hub.db under ${TOWONEL_DATA_DIR} (default)
postgresNTOWONEL_HUB_DB_DSN required

Pin ref.tag to a published chart release.

apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: towonel-node
namespace: towonel
spec:
interval: 15m
layerSelector:
mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip
operation: copy
ref:
tag: <chart-version>
url: oci://codeberg.org/towonel/charts/towonel-node

The chart renders a Deployment for hub-only. Mount a PVC under TOWONEL_DATA_DIR via persistence (the chart creates and mounts it) and keep one replica.

apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: towonel-hub
namespace: towonel
spec:
chartRef: { kind: OCIRepository, name: towonel-node }
interval: 30m
values:
global:
fullnameOverride: towonel-hub
edge:
enabled: false
controllers:
main:
replicas: 1
annotations:
reloader.stakater.com/auto: "true"
containers:
main:
env:
TOWONEL_DATA_DIR: /data
TOWONEL_HUB_PUBLIC_URL: https://hub.example.eu
envFrom:
- secretRef: { name: towonel-hub-secret }
persistence:
data:
enabled: true
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 1Gi
globalMounts:
- path: /data

The hub API listens on 8443, the hub↔edge link on 51444 and metrics on 9091 (override with hub.apiPort / hub.linkPort / hub.metricsPort). Probes target /v1/health and /v1/readyz automatically. TOWONEL_HUB_PUBLIC_URL is the external URL clients and the CLI use — :443 behind a gateway, or :8443 if the API port is exposed directly.

Drop the PVC, bump the replica count, and add the Postgres DSN. mTLS via cert-manager is the recommended setup with CloudNativePG.

spec:
values:
global:
fullnameOverride: towonel-hub
edge:
enabled: false
controllers:
main:
replicas: 2
annotations:
reloader.stakater.com/auto: "true"
containers:
main:
env:
TOWONEL_HUB_PUBLIC_URL: https://hub.example.eu
TOWONEL_HUB_DB_DRIVER: postgres
TOWONEL_HUB_DB_DSN: >-
postgresql://towonel_hub@pg-pooler-rw.towonel.svc:5432/towonel_hub?sslmode=verify-full&sslcert=/var/run/secrets/postgresql/tls.crt&sslkey=/var/run/secrets/postgresql/tls.key&sslrootcert=/var/run/secrets/root-ca/ca.crt
envFrom:
- secretRef: { name: towonel-hub-secret }
persistence:
postgres-certs:
type: secret
name: postgres-towonel-hub-cert
globalMounts:
- path: /var/run/secrets/postgresql
root-ca:
type: secret
name: root-ca
globalMounts:
- path: /var/run/secrets/root-ca

cert-manager issues the client cert with CN towonel_hub; pg_hba on the CNPG Cluster maps it via hostssl all all all cert clientcert=verify-full map=….

Leader election is on by default — only one replica is the active leader, the rest stand by. When the main DSN points at a pooler (as above), point leader election at the Postgres primary directly with TOWONEL_HUB_LEADER_DB_DSN.

Both roles are on by default. Leaving edge.enabled at its default renders a hostNetwork DaemonSet running hub and edge in one process — the single-node topology, equivalent to the Docker quick start. Add the edge env (TOWONEL_EDGE_ADVERTISED_ADDRESSES, …) alongside the hub env under controllers.main.containers.main.env. Split into separate releases (this page + the edge page) for HA.

Hub keys come from env via envFrom. Generate each with openssl rand -hex 32.

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: towonel-hub
spec:
secretStoreRef:
kind: ClusterSecretStore
name: onepassword
target: { name: towonel-hub-secret }
data:
- secretKey: TOWONEL_IDENTITY_KEY
remoteRef: { key: towonel-hub, property: NODE_IDENTITY_KEY }
- secretKey: TOWONEL_HUB_OPERATOR_API_KEY
remoteRef: { key: towonel-hub, property: OPERATOR_API_KEY }
- secretKey: TOWONEL_HUB_KEK
remoteRef: { key: towonel-hub, property: HUB_KEK }
- secretKey: TOWONEL_INVITE_HASH_KEY
remoteRef: { key: towonel-hub, property: INVITE_HASH_KEY }
- secretKey: TOWONEL_HUB_LINK_PSK
remoteRef: { key: towonel-hub, property: HUB_LINK_PSK }

Under Postgres each replica needs the same TOWONEL_IDENTITY_KEY — auto-generation on first boot would have each replica fork a different identity, which is why an explicit key is required.

The chart renders the Service but no route by default. Enable one with the native route value; it wires the backend to the hub Service on 8443 automatically.

route:
main:
enabled: true
hostnames: [hub.example.eu]
parentRefs:
- name: envoy-external
namespace: network
sectionName: https

:51444 (hub-link) stays in-cluster — only edges talk to it.

Behind a gateway the hub sees the proxy’s IP as the peer. Set TOWONEL_HUB_TRUSTED_PROXIES (CIDRs) so it honors X-Forwarded-For — otherwise per-IP rate limits and login lockouts key on the proxy and collapse every client to one bucket.

serviceMonitor.main.enabled: true scrapes the hub metrics port. monitoring.prometheusRule.enabled: true ships the hub alert rules, and monitoring.dashboards.enabled: true renders the Grafana dashboard (sidecar ConfigMap, or a GrafanaDashboard CR under monitoring.dashboards.grafanaOperator).

The web console (TOWONEL_HUB_WEB_ENABLED) needs TOWONEL_HUB_PUBLIC_URL and mail config; OIDC login adds the fields below.

env:
TOWONEL_HUB_OIDC_CODEBERG_REDIRECT_URI: https://console.example.eu/api/v1/auth/oidc/codeberg/callback
# TOWONEL_HUB_OIDC_CODEBERG_CLIENT_ID/SECRET, TOWONEL_MAIL_MAILJET_* via envFrom