Universe
One JAR. The whole cluster.
A single-JAR orchestrator that deploys and manages application instances across a cluster of nodes. The same artifact runs as the master or a wrapper, cluster state lives in embedded Hazelcast, and one template deploys to screen, tmux, Docker, or Kubernetes.
Control
- Operator CLI · HTTP · WebSocket console
- Templates versioned file trees · local, S3, Git
Master
- Ktor REST + WS :7000 bearer auth · rate limiting
- Cluster state Hazelcast IMap
Cluster
- Hazelcast :6000 peer to peer · IExecutor task dispatch
Wrappers
- Wrapper × N same JAR, role set by config
- Runtime screen · tmux · Docker · Kubernetes
The problem
Running a fleet of game servers means running a lot of short-lived, near-identical processes across several machines: start an instance from a template, hand it a port, watch its logs, stop it, repeat. The existing tools for this in the Minecraft world were heavy, tied to one hosting model, and hard to move from a laptop to a real cluster. I wanted one artifact that could run a two-node setup on bare metal today and a Kubernetes fleet tomorrow without rewriting the config.
The shape of it
Universe is a single fat JAR. Configuration decides what a node does when it starts:
- A master exposes the REST API and owns cluster state.
- Wrappers execute instances. Work reaches them as tasks dispatched through the cluster.
There is no separate control-plane binary to build, package, or version. Every node is the same JAR with a different config.json.
Cluster state without a coordinator
Cluster membership, state, and task routing run on Hazelcast embedded in the JAR. Masters and wrappers form a peer-to-peer cluster on port 6000; state is kept in Hazelcast IMaps and tasks are pushed to wrappers through IExecutor. Embedding it meant a cluster needs nothing else running to exist: no external etcd, no message broker to operate before the orchestrator itself can start. That mattered for the small-cluster case, where the operational cost of a coordinator would have been most of the cost of the whole system.
Templates instead of images
An instance is provisioned from a template: a versioned file tree with variable substitution. %PORT%, %INSTANCE_ID%, and custom variables are replaced at install time, and template rules control which files are copied and which are modified. Templates can live locally or in S3-backed storage through an extension, and can be synced from Git.
The payoff is that a template describes what to run, not where. The same template deploys to a screen session, a tmux session, a Docker container, or a Kubernetes pod. The runtime is one field in the instance configuration.
Runtimes as extensions
screen and tmux runtimes are built in. Docker and Kubernetes runtimes, S3 storage, PostgreSQL, MongoDB, and Redis databases, Prometheus metrics, a Tailscale integration that exposes mesh IPs as template variables, and GitOps sync with ArgoCD manifest export are all extensions.
An extension is a self-registering JAR dropped into ./extensions/. It implements an Extension interface and registers its providers in onLoad() through dependency injection. The rule that keeps the core honest: extensions depend on the api module and never on app. The core stays small and the surface area for a broken extension stays contained.
Operating it
Everything the console can do, the API can do. The master serves a Ktor REST API and a WebSocket console on port 7000 with bearer-token auth and rate limiting. Instance lifecycle is HTTP: create, change state, execute console commands, stream logs. Metrics are exposed for Prometheus and logs are structured, with Logback rotation for the file output.
POST /api/instances create an instance from a template
PUT /api/instances/{id}/state start, stop, restart
POST /api/commands/execute run a console command
WS /api/console interactive console and live logs
GET /api/cluster/nodes list cluster members
Minecraft as a first-class target
The original motivation was game servers, so Universe ships plugin APIs for Paper, Spigot, Velocity, BungeeCord, and Folia. Those live in their own modules so the orchestrator core has no dependency on Minecraft at all. It is a general instance orchestrator that happens to know game servers very well.
What I learned
The two decisions that carried the most weight were embedding the coordinator and separating templates from runtimes. The first made the two-node case cheap. The second made the fleet case possible without a second config format. Most of the remaining complexity is in the extension boundary, and keeping that boundary strict has been worth every argument with the type checker.
Universe is Apache 2.0. A licensed edition adds key-gated extensions and support.