Skip to content

Tech Stack

A short, honest list of every load-bearing technology in PrexorCloud, the role it plays, and the reasoning behind picking it. We do not pile on dependencies — every entry below is something we’d argue for on its own.

What you’ll learn

  • The languages and runtimes for each process
  • The frameworks and libraries that show up in the build files
  • Operational dependencies: MongoDB, Valkey, cosign, Prometheus
  • Why we picked each one over the obvious alternative

Process matrix

ProcessLanguageRuntimeFramework / library
ControllerJava 25OpenJDK 25Javalin 7 (HTTP), grpc-java (gRPC), Mongo Java driver, Lettuce (Valkey/Redis), Logback + SLF4J, Jackson, Prometheus simpleclient
DaemonJava 21OpenJDK 21grpc-java (client), ProcessBuilder (no framework), Logback + SLF4J
DashboardTypeScriptNode 22 + browserNuxt 4, Vue 3, OpenAPI-generated SDK, Pinia stores, ESLint + Vitest + Playwright
CLI (prexorctl)Go 1.22+static binaryCobra (commands), Charmbracelet libraries (TUI bits), goreleaser (cross-build + sign)
Server plugins (Paper / Spigot / Folia)Java 21host server JVMBukkit / Paper API
Proxy plugins (Velocity / Bungee)Java 21host proxy JVMVelocity / Bungee API

The controller targets Java 25 because the controller-side code base opts in to recent JVM ergonomics (records, pattern matching, virtual threads). cloud-common and the daemon target Java 21 so plugin-side modules running on long-tail server JDKs can consume the shared API jar.

External services

ServicePurposeWhy this one
MongoDB 6.0+Durable platform state. Self-hosted; PrexorCloud does not embed it.Document shape maps onto deeply-nested per-feature variable data (composition plans, module manifests, workflow intent). Switching to Postgres would mean either a tableful-of-JSON anti-pattern or a rigid schema that fights every new feature.
Valkey 7.2+ (Redis 7+)Coordination. Required in production profile.TTL semantics, lease primitives, pub/sub. Mongo’s TTL index works but is much heavier per contention. Valkey is BSD-3; Redis BSL. We default to Valkey for the licensing question, but the controller speaks the Redis protocol so operators can keep using Redis if they already do.
PrometheusMetric scrape targetStable exposition format, ubiquitous, low operational overhead.
Cosign + Fulcio + RekorRelease and module signingNo long-lived signing keys. Closest thing to a standard for signing artefacts in 2026.

What we deliberately don’t ship

Not in v1Why
Spring / Guice / DaggerDI frameworks hide the dependency graph. Hand-wired PrexorCloudBootstrap keeps the ~80-component graph readable in one file.
OpenTelemetryDistributed tracing earns its keep with dozens of services. PrexorCloud is two services with one well-defined gRPC contract. Prometheus + structured logs cover the questions operators ask.
Helm / Kubernetes operatorCompose-first install fits the audience (MC operator teams). K8s pods around per-MC-instance JVMs is awkward and slow.
Grafana dashboard packMaintaining dashboards as code is a real burden. Metrics are stable and well-named; build the panels you need.
OIDC / SAML / SCIM / MFASingle-tenant local-user + JWT only. The audience is 1–10-operator teams; SSO complexity outweighs benefit at that scale.
WASM modulesModules ship as JVM jars. Operators already have JVM expertise, and the threat model is signed-bundle integrity, not hostile-module sandboxing.
GitOps reconciliation loopImperative templates and groups via REST / CLI / dashboard. GitOps helps at one scale and hurts at another.

Each of these is an explicit architectural decision, not an accident of timeline.

Build and CI

ToolPurpose
Gradle (Kotlin DSL)Multi-project Java build. Toolchains pin Java 25 / 21 per module.
pnpmDashboard + module-SDK + auxiliary Node projects.
Go modulesCLI build.
GitHub ActionsCI for tests, builds, releases. release.yml ships cosign-signed prexorctl binaries; release-images.yml ships cosign-signed multi-arch GHCR images on v*.
goreleaserCLI cross-compile + sign + publish.
CosignKeyless signing of release artefacts and operator-side verification.
TrivyVulnerability scan against built images.
SyftCycloneDX SBOM per image.
Sigstore RekorTransparency log; offline SET enforcement available via modules.signing.rekor.policy=REQUIRE_SET.

Test strategy

TierWhereWhat
Unitper-module src/test/javaFast, no external services, mock at boundaries only.
Integrationcloud-test-harnessBoots a real controller against real Mongo + Valkey; covers REST, gRPC, scheduler, recovery, modules.
Recovery harnesscloud-test-harness/RecoveryTestStandby promotion drills (drain, deployment, placement-time, in-flight module mutation).
DR drill:cloud-test-harness:drDrill (@Tag("dr"))Backup → wipe → restore → state match. Runs nightly via .github/workflows/nightly.yml :: dr-drill.
Perf baselines:cloud-test-harness:perfBaselines (@Tag("perf"))Cold start, coordination latency, SSE latency, scheduler tick at 1k groups. Runs nightly; reports drift > 25%.
DashboardVitest + PlaywrightComponent + e2e against a mocked controller and a real dev controller.
CLIGo testIncludes goreleaser-check to keep release config from drifting.

Runtime requirements (bare metal)

ComponentMinimumNotes
Controller hostLinux x86_64 (Debian/Ubuntu, RHEL/Fedora, openSUSE, Arch)macOS / Windows hosts are not supported as controllers.
Daemon hostLinux x86_64Same.
JavaOpenJDK 21+ for daemon, 25+ for controllerprexorctl setup installs via the distro package manager when missing.
MongoDB6.0+Self-hosted; replica set recommended for HA.
Valkey / RedisValkey 7.2+ / Redis 7+Required in production profile.

Library highlights

A non-exhaustive list of dependencies that shape behaviour:

  • Javalin 7 — small Kotlin/Java HTTP framework with first-class WebSocket / SSE. We disable WS (see ADR 11) and lean on SSE.
  • grpc-java + Netty — bidirectional streaming for daemon connections, with ReloadableServerSslContext for hot CA rotation.
  • Lettuce — non-blocking Redis-protocol client. We use synchronous primitives at the call sites; Lettuce’s connection multiplexing keeps that cheap.
  • Mongo Java driver — synchronous flavour. Connection pool tuned via the URI; we don’t wrap it.
  • Logback + SLF4J + JsonLogEncoderHUMAN and JSON log formats; RequestIdMiddleware plumbs requestId through MDC.
  • Jackson — only Jackson, no Gson or alternatives. Records map cleanly without runtime type-magic.
  • Prometheus simpleclient — metric registration site is MetricsCollector. Module metrics are hand-rendered exposition, not via the client library, to keep module dependencies minimal (ADR 16).
  • bcrypt — password hashing.
  • Bouncy Castle (where needed) — Cosign / Rekor signature verification, mTLS CA operations.
  • Nuxt 4 / Vue 3 / Pinia — dashboard. SDK is generated from the controller’s OpenAPI spec.
  • Cobra — CLI command surface. Output formatters live in cli/internal/output/.

Versioning

  • PrexorCloud release versions — semver; minor versions are additive, major versions can break.
  • gRPC contractcloud-protocol is bumped only on incompatible changes; the controller and daemon negotiate the protocol version at handshake.
  • Module SDKdashboard/packages/module-sdk ships its own version. The compat matrix lives at dashboard/packages/module-sdk/COMPAT.md.
  • Schema — Mongo schema migrations run on startup and are logged (migration applied: <name>). Release notes call out migrations that need data backfill.

Why we chose each thing — short list

PickOverWhy
Java 25 controllerJava 21Records + pattern matching + virtual threads pay back the upgrade cost. Daemon stays on 21 for plugin-side compat.
Hand-wired PrexorCloudBootstrapSpring / GuiceReadable graph, faster startup, no annotation magic.
Javalin 7Spring BootMinimal HTTP layer, native SSE, no auto-config.
MongoDBPostgreSQLDocument model fits composition plans + workflow intent + module storage.
ValkeyRedisBSD-3 license; same protocol.
Cosign keylessCustom signing schemeWe do not maintain a private key.
PrometheusOpenTelemetryTwo-service control plane, one gRPC contract.
SSEWebSocketServer → client only; Last-Event-ID resumption built in.
ComposeHelm chartOperator audience already has Docker; K8s around MC processes is awkward.
Capability handlesShared “internal” types moduleAvoids the “every module depends on every module” trap.

Next up