Isolated Minecraft minigame servers, on demand. REST API over Docker, written in Go.
Note
Requires Docker running on the host and Go 1.22+.
# 1. Clone and build
git clone https://github.com/mitsuakki/minestrate
cd minestrate
go build -o minestrate .
# 2. Configure
cp minestrate.example.yaml minestrate.yaml
# edit minestrate.yaml
# 3. Run
./minestrate --config minestrate.yamlRunning a Minecraft minigame lobby means spinning up isolated game servers on player demand and tearing them down cleanly when the game ends.
Doing this by hand doesn't scale. Doing it with a full Kubernetes cluster is overkill.
minestrate is a single Go binary that exposes a REST API over the Docker socket.
- One
POSTcreates an isolated PocketMine-MP server with its own network, port, and lifecycle. - One
DELETEdrains and removes it.
flowchart TD
%% Node Definitions
A["Lobby"] -->|"POST /servers"| B["REST Gateway\nJWT · rate limit · TLS"]
B --> C["FSM Orchestrator\nworker pool"]
C --> D["Resource Allocator\nports · subnets"]
C --> E["Docker Engine\nUnix socket"]
E --> F["container A\n/28 · port"]
E --> G["container B\n/28 · port"]
E --> H["container C\n/28 · port"]
%% Class Assignments
class A frontend
class B,C gateway
class D,E infra
class F,G,H containers
%% Style Definitions
classDef frontend fill:#f5f6fa,stroke:#0984e3,stroke-width:2px,color:#2d3436
classDef gateway fill:#f5f6fa,stroke:#6c5ce7,stroke-width:2px,color:#2d3436
classDef infra fill:#f5f6fa,stroke:#e17055,stroke-width:2px,color:#2d3436
classDef containers fill:#f5f6fa,stroke:#00b894,stroke-width:2px,color:#2d3436
%% Link Styling
linkStyle default stroke:#b2bec3,stroke-width:2px,color:#d63031
Important
All endpoints require a Bearer JWT issued by the lobby.
POST /servers
Request:
POST /servers
Content-Type: application/json
{
"game": "skywars",
"players": 8
}GET /servers/{id}
{
"id": "srv_01j4k",
"state": "running",
"address": "10.0.0.1",
"port": 25565,
"players": 4,
"created": "2024-11-01T14:22:00Z"
}DELETE /servers/{id}
Transitions the server to draining. Players are notified, then the container is stopped and all resources are released. Returns 202 Accepted.
GET /servers
Returns the projection of all active servers. Stopped servers are not included.
The allocator maintains a bitset over the configured port range and reserves ports atomically with a CAS operation. Two servers can never share a port by construction; it is an invariant enforced at the data structure level, not a runtime check.
The five states and their transitions are the only mutations allowed on a server record. Every HTTP handler maps to exactly one transition. This makes the system straightforward to reason about: if you know the current state, you know every possible next state.
Given a measured mean container start time
Beyond 0.8 utilisation, queue wait times grow non-linearly. The default of 4 workers handles up to
Each container receives a dedicated /28 subnet carved from the configured block. Subnets are disjoint by construction; game servers cannot reach each other's networks unless explicitly routed.
JWT verification is
The start_timeout value should be set to
# minestrate.yaml
env: prod
server:
port: 8080
tls_cert: "" # Optional: path to your cert.pem
tls_key: "" # Optional: path to your key.pem
auth:
jwt_secret: "your-secret"
token_ttl: 300
docker:
socket: "" # Default platform socket (e.g. /var/run/docker.sock or npipe:////./pipe/docker_engine)
image: pmmp/pocketmine-mp:latest
cpu_limit: 1.0
memory_limit: 512m
orchestrator:
workers: 4
max_servers: 100
start_timeout: 30
ports:
range_start: 19132
range_end: 20132
network:
subnet_block: 172.20.0.0/14On this hardware (AMD Ryzen 7 4800H, 8 Cores, 16GB RAM, Windows 11 Pro), the mean Docker container start time (alpine image:
| Metric | Value |
|---|---|
| Mean start time ( |
1.18s |
| Throughput ( |
0.85 starts/sec |
The following benchmark measures the throughput of the orchestrator worker pool with varying numbers of workers (
| Workers ( |
Throughput (starts/sec) | Scaling |
|---|---|---|
| 1 | 9.94 | 1.0x |
| 2 | 19.89 | 2.0x |
| 4 | 38.79 | 3.9x |
These values confirm that the system scales linearly with the number of workers until it hits the host's I/O or Docker socket bottlenecks.
These values are used to tune the worker pool size and start timeout as described in the Design Decisions section.
MIT