Adventures in Modded Minecraft Server Deployment
TPS? Who needs TPS?
With modded games, getting them to work can take as much time as the game itself—sometimes more. Minecraft is no exception. I’m part of a group of friends who play… probably too much Minecraft. Naturally, we mod it—heavily. And since we want to play together, we run a server. But we tend to get bored of our current mod setups, so we switch things up frequently. Sounds fun, right? It is! Just one problem—I’m the tech support. It’s up to me to keep the server and clients in sync and make sure everything’s running smoothly. I don’t mind, though; I’m a bit of a software nerd, and I’m all about making things as efficient as possible.
So, I built a pipeline for modded Minecraft server/client creation and deployment using Packwiz, Prism Launcher (a fork of MultiMC), Docker (with the itzg/minecraft-server container), and a pair of custom apps built with GoLang—a “manager” program for the server, and a “toolkit” program to assist with deploying packs. (Check out my other blog post about the pipeline here).
The First Day:
The pipeline was working fine until I deployed a particularly CPU-intensive server. The result? Substantial tick lag made the game unplayable.
Understanding Minecraft Lag:
-
Tick Lag: Minecraft’s game world updates in “ticks,” ideally achieving 20 ticks per second (TPS). Tick lag happens when the server can’t maintain this rate, often due to high CPU usage. This results in delayed actions like blocks breaking and reappearing, mobs moving erratically, delayed chat messages, and rubber-banding (where players or mobs seem to teleport or snap back to previous positions).
-
Client Lag: This type of lag is experienced by the player on their local machine, usually due to insufficient hardware, high graphics settings, or resource-intensive mods. Symptoms include low FPS (frames per second) and slow responsiveness to player input.
-
Network Lag: This occurs when the server struggles to communicate with clients, often due to network issues on the end-user’s side. It can be identified as not all clients will be affected simultaneously.
If you’re wondering how I know so much about Minecraft servers, well, I’ve been managing them for a while. Along the way, I’ve picked up a somewhat niche set of knowledge and skills.
I tried fixing the issue by removing mods, tweaking JVM flags, and even bypassing Docker entirely by running the server outside a container. But no dice. During this process, I found my manager app to be rather inflexible when things went wrong. It was built on the assumption that things would either work perfectly or not at all, so I ended up creating multiple shell scripts to handle the situation.
After all that, I decided to step away from the issue for the day.
The Second Day:
By this point, it was clear that my VPS’s CPU was underpowered, and Docker wasn’t the issue—there was no meaningful performance uplift when running Minecraft outside the container. The only way forward was to minimize CPU load aggressively.
After some Googling, I found a helpful GitHub repository (YouHaveTrouble/minecraft-optimization) that offers optimization tips for more standard servers—not the overextended “many mods in a handbasket” setup I was dealing with—but it still pointed me in the right direction: the server.properties file.
In modern versions of Minecraft, you can control the simulation and view distances independently within server.properties. I set the view-distance to 8 and simulation-distance to 4, which resolved the lag issue—must have taken off enough CPU load.
Troubleshooting the previous day revealed that the server-side “manager” app could be replaced with a more flexible shell script. The app’s main function was just to pull the Packwiz definition file (pack.toml) and create a Docker container from it anyway.
Packwiz works similarly to Git LFS, using “pointer files” for mods, resource packs and other large files. Smaller files (such as configs) are stored directly in the repository. Pointer files are small files that contain links to the locations where the actual resources are hosted, and checksums for the files to ensure security. The
pack.tomlfile acts as a directory. It tells the launcher where all the small files and pointer files are located, and tracks the checksums of all files to ensure integrity.
I wrote a shell script to pull pack.toml, copy some files (server.properties, whitelist.json) from a predefined location, back up server data, and create or recreate a Docker container for the server.
Check out the GitHub repository with the shell files
The shell script requires the zip package. Also, if you’re interested, feel free to read my other blog post about the pipeline. And of course, the obligatory “if you don’t know what it’s doing, don’t run it” warning applies.