Skip to content

Hub on Kubernetes

Hub-only mode (TOWONEL_HUB_ENABLED=true, TOWONEL_EDGE_ENABLED=false). Edges run separately — see Edge on Kubernetes.

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

Driver Replicas DSN
sqlite 1 hub.db under ${TOWONEL_DATA_DIR} (default)
postgres N TOWONEL_HUB_DB_DSN required
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: towonel-hub
spec:
chartRef: { kind: OCIRepository, name: towonel-hub }
interval: 30m
values:
controllers:
towonel-hub:
type: statefulset
replicas: 1
statefulset:
volumeClaimTemplates:
- name: data
accessMode: ReadWriteOnce
size: 1Gi
globalMounts: [{ path: /data }]
annotations:
reloader.stakater.com/auto: "true"
containers:
app:
image:
repository: codeberg.org/towonel/towonel-node
tag: 0.1.1
env:
TOWONEL_HUB_ENABLED: "true"
TOWONEL_EDGE_ENABLED: "false"
TOWONEL_DATA_DIR: /data
TOWONEL_HUB_LISTEN_ADDR: 0.0.0.0:8443
TOWONEL_HUB_LINK_LISTEN_ADDR: 0.0.0.0:51444
TOWONEL_HUB_PUBLIC_URL: https://hub.example.eu
envFrom:
- secretRef: { name: towonel-hub-secret }
probes: &hub-probes
startup:
enabled: true
custom: true
spec:
httpGet: { path: /v1/health, port: 8443 }
periodSeconds: 5
failureThreshold: 60
liveness:
enabled: true
custom: true
spec:
httpGet: { path: /v1/health, port: 8443 }
readiness:
enabled: true
custom: true
spec:
httpGet: { path: /v1/readyz, port: 8443 }
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { memory: 512Mi }
service:
app:
ports:
hub-api: { port: 8443 }
hub-link: { port: 51444 }

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

spec:
values:
controllers:
towonel-hub:
replicas: 2
containers:
app:
env:
TOWONEL_HUB_ENABLED: "true"
TOWONEL_EDGE_ENABLED: "false"
TOWONEL_HUB_LISTEN_ADDR: 0.0.0.0:8443
TOWONEL_HUB_LINK_LISTEN_ADDR: 0.0.0.0:51444
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
persistence:
postgres-certs:
type: secret
name: postgres-towonel-hub-cert
defaultMode: 0o600
globalMounts: [{ path: /var/run/secrets/postgresql }]
root-ca:
type: secret
name: root-ca
defaultMode: 0o440
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=….

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.

route:
app:
hostnames: [hub.example.eu]
parentRefs:
- name: envoy-external
namespace: network
sectionName: https
rules:
- backendRefs:
- identifier: app
port: 8443

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

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