The development stack runs the whole network locally in Docker:
| Service | What it is |
|---|---|
proxy |
Velocity proxy, the only server players connect to |
hub-lobby-01 |
Paper server of the lobby hub world |
hub-north-01 |
Paper server of the north hub world |
hub-south-01 |
Paper server of the south hub world |
home |
Paper server of the player homes |
pack-01 |
Paper server that builds the CraftEngine resource pack and uploads it to SeaweedFS |
seaweedfs, seaweedfs-admin |
S3 storage the resource pack is downloaded from, and its admin UI |
odyssey |
This repository's microservice, built from its Dockerfile |
core |
surf-core microservice, pulled from ghcr.io/slne-development/surf-core-microservice |
polar-paper |
surf-polar-paper microservice, pulled from ghcr.io/slne-development/surf-polar-paper-microservice |
postgres |
One database (surf) for everything, with a schema per microservice and for LuckPerms |
rabbitmq, valkey |
Messaging between the plugins and the microservices |
- Java 25
- Docker with Docker Compose. The ghcr images are public, no login needed.
- Free ports 25565, 5432, 5672, 6379 and 15672. Stop other development stacks first, or change the ports in
.env.
-
Put the server plugins into the run folders. The stack deploys the odyssey plugins itself, everything else has to be put into the
pluginsfolders below once. The folders are gitignored.Server Plugins folder proxysurf-odyssey-runtime/surf-odyssey-runtime-velocity/run/pluginshub-lobby-01surf-odyssey-runtime/surf-odyssey-runtime-paper/surf-odyssey-runtime-paper-hub/surf-odyssey-runtime-paper-hub-lobby/run/hub-lobby-01/pluginshub-north-01surf-odyssey-runtime/surf-odyssey-runtime-paper/surf-odyssey-runtime-paper-hub/surf-odyssey-runtime-paper-hub-north/run/hub-north-01/pluginshub-south-01surf-odyssey-runtime/surf-odyssey-runtime-paper/surf-odyssey-runtime-paper-hub/surf-odyssey-runtime-paper-hub-south/run/hub-south-01/pluginshomesurf-odyssey-runtime/surf-odyssey-runtime-paper/surf-odyssey-runtime-paper-home/run/pluginspack-01surf-odyssey-runtime/surf-odyssey-runtime-paper/surf-odyssey-runtime-paper-pack/run/pack-01/pluginsThe hub column stands for each of the four
hub-*servers.Plugin proxy hub home pack-01 Download surf-api x x x x surf-api-velocity-server/surf-api-paper-serverfrom surf-apisurf-rabbitmq x x x x surf-rabbitmq-velocity/surf-rabbitmq-paperfrom surf-rabbitmqsurf-redis x x x x releases surf-core x x x x releases CommandAPI x x x x releases, Velocity or Paper jar LuckPerms x x x x luckperms.net, Velocity or Bukkit jar packetevents x x x x modrinth, Velocity or Spigot jar HuskSync x x modrinth CraftEngine x x x craft-engine-bukkit-pluginjarMiniPlaceholders x modrinth polarpaper x releases surf-polar-paper-runtime-paper x releases -
Start the stack.
./gradlew startDevelopmentStack
This builds the plugins and copies them into the run folders, checks that every plugin above is there (and names the missing ones), builds the odyssey microservice image, pulls the other microservices and starts everything. The first start takes a few minutes; Paper and Velocity are downloaded into the run folders.
-
Join
localhost:25565. You land on the lobby (hub-lobby-01). -
Give yourself permissions. LuckPerms of the proxy and every Paper server share their storage, so one command from any console applies everywhere:
docker compose exec -u 1000 hub-lobby-01 mc-send-to-console lp user <name> permission set '*' true
./gradlew startDevelopmentStack # (re)build and start everything, also picks up changes to compose.yaml
./gradlew deployDevelopmentPlugin # rebuild the odyssey plugins and restart the running servers with them
./gradlew stopDevelopmentStack # stop the stack, data is kept
./gradlew cleanDevelopmentStack # stop the stack and delete the database, RabbitMQ and Valkey dataConsoles are attached with docker compose attach proxy (or hub-lobby-01, hub-north-01,
hub-south-01, home, pack-01, odyssey, core, polar-paper) and
detached with Ctrl+P, Ctrl+Q. Logs: docker compose logs -f <service>. For the microservices, run these through the
Gradle tasks or set COMPOSE_FILE as described below, since they live in a generated compose file.
| Service | Address |
|---|---|
| Proxy | localhost:25565 |
| RabbitMQ UI | http://localhost:15672 |
| PostgreSQL | localhost:5432 |
| Valkey | localhost:6379 |
Users and passwords are surf / surf. Copy .env.example to .env to change ports, credentials, memory and the
Velocity forwarding secret.
-
compose.yamlholds the infrastructure, the proxy and the servers. The servers run from theirrunfolders. -
build.gradle.ktsconfigures thedevelopmentStackextension ofbuild-logic:server(...)names a compose service, the odyssey plugin project deployed to it and the plugins it needs.microservice(...)adds a microservice. It either runs a publishedimage, pulled on every start, or is built from adockerfileof a local Gradle build.schemagives it a schema in the shared database,redis = trueconnects it to Valkey. The microservices are written tobuild/development-stack/microservices.compose.yaml, which every Gradle task passes to compose next tocompose.yaml. To use plaindocker composewith them, setCOMPOSE_FILE=compose.yaml:build/development-stack/microservices.compose.yaml(;instead of:on Windows).schemaslists further schemas to create, e.g.luckperms.
-
docker/holds config files synced into the run folders on every start, with${CFG_...}placeholders filled from the container environment:docker/velocity:velocity.toml, the forwarding secret and the surf-core config of the proxydocker/paper/config:paper-global.ymlof the Paper servers, enabling Velocity forwardingdocker/paper/plugins: plugin configs of every Paper server, e.g. the surf-core server name per server and the CraftEngine resourcesdocker/paper/hub/plugins: the CraftEngine config of the hub servers and home, which only sends the packdocker/paper/pack/plugins: the CraftEngine config ofpack-01, which builds and uploads the packdocker/paper/home/plugins: plugin configs of home only, e.g. surf-polar-paper with DATABASE storage
-
docker/worldsholds the hub worlds; every hub server copies its world from there on each start.Change these files instead of the copies in the run folders; a copy is only replaced when its source is newer.
-
LuckPerms is configured through the
LUCKPERMS_*variables incompose.yaml.