Components
Frontend
Next.js 16 (App Router) + React 19, TypeScript, Tailwind, shadcn/ui.
Backend
Go with the Chi router; a dynamic Kubernetes client per supervisor.
PostgreSQL
Control-plane state: supervisors, cluster templates, addons, IAM policies/ groups, audit logs, cached data.
ClickHouse
Network-flow analytics powering the security topology and forensics views.
Request pipeline
Every API request passes through an ordered middleware chain before reaching a handler:1
Tracing + access log
An OpenTelemetry span (named after the route pattern) and a structured access log line.
2
Recoverer
Panics are caught and turned into
500s instead of crashing the process.3
CORS
Cross-origin handling scoped to the configured frontend origin.
4
Session
Validates the
k8s-gate-session cookie (or a Bearer API token) and puts the user’s identity and groups into the request context.5
Audit
Mutating requests (POST/PUT/DELETE) are recorded with user, action, resource, status, and duration.
6
Authorization (IAM)
The route is resolved to an action + KRN and checked against the user’s policies. See Permissions model.
403 does — and either way the attempt is already recorded, because audit sits ahead of authorization in the chain. A denied request is in the audit log with the user who made it, which is what makes the log useful for “who tried” and not only “who succeeded”.
The order is deliberate throughout: session precedes audit so the log has an identity to attribute to, and authorization is last so it can resolve the route into the same action and KRN vocabulary the log already recorded.
Two deliberate exceptions to that chain
WithMCP_ENABLED=true the backend also serves /mcp, and the IAM middleware skips it. That is not a hole: each tool declares its own method and path and re-dispatches internally, so the work is authorized as the route it actually calls. The audit middleware skips /mcp for the same reason — the re-dispatched handler is what gets recorded, so a tool’s effect appears under the real route rather than under /mcp.
The second exception is narrower and worth knowing if you ever wonder why a write is missing from the log: API-token minting is never audited, because the response body carries the plaintext secret and the audit log must not capture it.
Multi-supervisor model
Celum manages many supervisor clusters at once. Each supervisor is a kubeconfig — provided as a file (filename = supervisor name) or stored in the database. An empty supervisor name resolves to a synthesized__default__. Kubernetes clients are cached per supervisor, and every supervisor call is tagged with its name in traces. See Supervisors & clusters for the full model.
Provider detection
A cluster’s provider is read from its Cluster APIspec.infrastructureRef.kind and mapped to a friendly name — for example:
Unmapped kinds fall back to the kind name with the
Cluster suffix stripped.
Data fetching
The frontend uses two helpers, never rawfetch:
The client API base URL is injected at request time via a runtime-config script (
window.__APP_CONFIG__), so the same build runs against any environment.
Observability
The backend emits OpenTelemetry traces over OTLP/HTTP whenOTEL_EXPORTER_OTLP_ENDPOINT is set (a no-op tracer otherwise, so there’s no overhead when unconfigured). Spans cover HTTP handlers, per-supervisor Kubernetes calls, and PostgreSQL/ClickHouse queries, and structured logs carry the active trace ID for correlation.
Performance patterns
Read-only endpoints that hit a supervisor’s API use a stale-while-revalidate cache: fresh values are served immediately, stale values are returned while a refresh runs in the background, and a cold miss is bounded by a short timeout with an empty fallback — so one unreachable supervisor can’t hang a page. Helm release listings use a fast path that reads release Secrets directly, avoiding the Kubernetes API discovery cost.A consequence worth knowing: a page can show data from a supervisor that has since become unreachable. Values are served stale while a refresh runs behind them, and a cold miss returns empty rather than an error.
Backend layout
The Go backend is organized underbackend/internal/:
Plus integration packages for DNS, IPAM, GitOps, and related services.
Related
Supervisors & clusters
How supervisors are discovered, named, and cached.
Permissions model
How a request becomes an action + KRN, and how it is evaluated.