Server groups
Capacity, routing, matchmaking, retry, and timeout policy for a class of servers.
A server group defines traffic policy for one class of Minecraft server. Each .yml or .yaml
file in this directory is parsed at orchestrator startup and synchronized to PostgreSQL.
There are two schemas:
hubgroups run shared lobby servers and route players by current load.minigamegroups match queued parties into isolated sessions.
Invalid configuration stops orchestrator startup. The loader does not watch this directory, so restart the orchestrator after every change.
Common fields
Every group starts with these fields:
id: skywars-solo
type: minigame
enabled: true
variants:
- id: sw-1s-dome
enabled: true
weight: 60
- id: sw-1s-japan
enabled: true
weight: 40| Field | Required | Default | Rules |
|---|---|---|---|
id | Yes | None | Unique identifier with 2 to 63 lowercase letters, digits, or dashes |
type | Yes | None | hub or minigame |
enabled | No | true | Disabled groups receive no new traffic |
variants | Yes | None | Array of final variant references. IDs cannot repeat inside a group |
variants[].id | Yes | None | Existing final layer ID from templates/ |
variants[].enabled | No | true | Disabled references remain synchronized but are not selected |
variants[].weight | Yes | None | Positive integer selection weight |
An enabled group needs at least one enabled variant. A disabled group may have no enabled variants. The same final variant may belong to several groups with different weights.
Weights are relative, not percentages. EnderCloud first looks for variants that are underrepresented in the current warm pool. It uses weight when choosing among otherwise suitable variants. Equal weights give equal preference over time.
Capacity
All groups require the complete capacity block:
capacity:
minimum_instances: 0
maximum_instances: 20
minimum_warm_instances: 1
maximum_warm_instances: 4| Field | Minimum | Meaning |
|---|---|---|
minimum_instances | 0 | Floor for active physical instances in the group |
maximum_instances | 1 | Strict ceiling for normal active physical instances |
minimum_warm_instances | 0 | Floor for open instances that are ready or starting |
maximum_warm_instances | 0 | Ceiling for open instances kept ahead of demand |
The limits must satisfy:
minimum_instances <= maximum_instances
minimum_warm_instances <= maximum_warm_instances <= maximum_instancesLifecycle states CREATING, STARTING, RUNNING, and DRAINING count as active for normal
capacity. Warm pending instances are CREATING or STARTING with availability OPEN. Warm ready
instances are RUNNING and OPEN.
During host maintenance, one replacement per group may temporarily exceed
maximum_instances. This bounded surge prevents an open source instance from draining before its
replacement is ready.
Common timeouts
Every group requires these six durations:
timeouts:
startup: 90s
drain: 15m
cancelled_drain: 10s
shutdown: 20s
transfer: 20s
player_stale: 30sGroup durations are positive integers followed by ms, s, m, or h. Values such as 500ms,
45s, and 4h are valid. Bare numbers and day suffixes are not valid in group YAML.
| Field | Starts when | Expiry behavior |
|---|---|---|
startup | Docker has started and the instance enters STARTING | Fail the instance if Paper has not sent SERVER_READY |
drain | Normal instance drain begins | Stop the instance even if players are still observed |
cancelled_drain | A minigame session is cancelled | Bound active hub evacuation before forced stop |
shutdown | The instance enters STOPPING | Bound graceful Minecraft shutdown before forceful Docker cleanup |
transfer | A transfer command is created | Expire the command and release session players that did not arrive |
player_stale | Paper last reports a player present | Remove the player from observed counts and mark session presence left |
The orchestrator persists an absolute deadline when an operation starts. Editing the timeout does not alter deadlines already stored in PostgreSQL. See the timeout reference.
Hub groups
A hub group adds routing and may set timeouts.instance_lifetime:
id: hub
type: hub
enabled: true
variants:
- id: hub
enabled: true
weight: 100
capacity:
minimum_instances: 2
maximum_instances: 5
minimum_warm_instances: 2
maximum_warm_instances: 4
routing:
maximum_players_per_instance: 100
target_players_per_instance: 70
timeouts:
instance_lifetime: 4h
startup: 90s
drain: 5m
cancelled_drain: 10s
shutdown: 20s
transfer: 20s
player_stale: 30s| Field | Required | Default | Rules |
|---|---|---|---|
routing.maximum_players_per_instance | Yes | None | Positive integer and strict per-hub routing limit |
routing.target_players_per_instance | Yes | None | Positive integer no greater than the maximum |
timeouts.instance_lifetime | No | 4h | Maximum age before replacement and drain |
The target is a soft aggregate scale-out threshold. When observed players reach the combined
target capacity of running and starting hubs, the controller requests another hub up to
maximum_instances. Players already above the target stay in place. New transfers still choose
the least-loaded open hub until its strict maximum is reached.
When a running hub reaches its persisted lifetime deadline, EnderCloud starts one replacement using current variant selection. It drains the expired hub only after the replacement reports ready. If no capacity slot is available, the expired hub remains open. One renewal runs per group at a time.
Minigame groups
A minigame group adds a matchmaking policy and two timeouts:
id: skywars-solo
type: minigame
enabled: true
variants:
- id: sw-1s-dome
enabled: true
weight: 100
- id: sw-1s-japan
enabled: true
weight: 100
matchmaking:
minimum_players: 4
maximum_players: 12
team_count: 12
team_size: 1
candidate_window: 20
team_balance:
minimum_players_per_team: 0
maximum_team_spread: 1
capacity:
minimum_instances: 0
maximum_instances: 20
minimum_warm_instances: 1
maximum_warm_instances: 4
timeouts:
startup: 90s
drain: 15m
cancelled_drain: 10s
shutdown: 20s
transfer: 20s
player_stale: 30s
instance_acquisition: 45s
lobby_stale: 135sMatchmaking fields
| Field | Required | Default | Rules |
|---|---|---|---|
minimum_players | Yes | None | Positive integer no greater than maximum_players |
maximum_players | Yes | None | Positive integer no greater than team_count * team_size |
team_count | Yes | None | Positive integer number of teams |
team_size | Yes | None | Positive integer maximum players in one team and maximum party size |
candidate_window | No | 20 | Positive integer number of oldest FIFO entries considered per tick |
team_balance.minimum_players_per_team | No | 0 | Integer from 0 through team_size |
team_balance.maximum_team_spread | No | team_size | Integer from 0 through team_size |
The matchmaker never splits a queue entry. A party larger than team_size is rejected before
matchmaking. Feasible profiles must fit every atomic party, the player limits, and the balance
rules.
minimum_players_per_team can require every used team to reach a floor.
maximum_team_spread bounds the difference between the largest and smallest team in a feasible
profile.
For a 4v4v4v4 mode that may start with eight players, requires at least one player per team, and allows a spread of at most two:
matchmaking:
minimum_players: 8
maximum_players: 16
team_count: 4
team_size: 4
candidate_window: 32
team_balance:
minimum_players_per_team: 1
maximum_team_spread: 2Minigame timeouts
| Field | Required | Starts when | Expiry behavior |
|---|---|---|---|
timeouts.instance_acquisition | Yes | A feasible session needs an instance but none is available | Cancel the session if no instance can be reserved |
timeouts.lobby_stale | Yes | Transfers to the reserved instance begin | Cancel a lobby that never progresses to GAME_STARTING |
The minigame plugin has sole authority to publish GAME_STARTING. EnderCloud has no partial-start
timer. lobby_stale is a watchdog for an abandoned lobby, not permission for the orchestrator to
start a game.
Variant references
Each referenced ID must resolve to one immediate directory under templates/ with a valid
variant.yml. Because it is a final variant, that descriptor must declare a positive revision.
Its ordered layer stack must resolve docker.image, docker.memory, and docker.cpu.
The group owns enabled and weight. Those fields are invalid inside variant.yml.
After changing effective template files or runtime settings:
- Increment the final variant revision.
- Restart the orchestrator.
- Confirm the new revision and checksum in the dashboard.
- Watch startup status before routing production traffic to it.
Existing instances keep their current materialized runtime. The revision affects new instance records.
Adding a group
- Copy the closest existing YAML file.
- Choose a unique ID and the correct
type. - Set all common capacity and timeout fields.
- Add
routingfor a hub ormatchmakingplus its two timeouts for a minigame. - Reference at least one complete final variant with a positive weight.
- Keep the group disabled until its server JAR, plugins, maps, and configuration are complete.
- Restart the orchestrator and check the configuration synchronization log.
The group filename does not define its ID. Use the same stem as the ID anyway, because it makes operator searches and reviews less error-prone.
Removed fields
The loader rejects the old lifecycle block, matchmaking timeout aliases, partial_start, and
timeouts.ineligible_lobby. Use the canonical timeouts block and
matchmaking.team_balance. Startup errors name the expected replacement.