If you have kids that are old enough to game on any sort of device with a screen, you’ve probably been asked about virtual Lego kits. And I don’t mean the various branded video games like LEGO Worlds or the LEGO Star Wars games. No, I’m talking about something far more addictive – Minecraft.
My kids are Minecraft fanatics. They could play for hours on end while creative how-tos and “Let’s Play” YouTube videos loop non-stop in the background. And they claim they want to play Minecraft together, although that’s more theory than actual practice in the end. They also like to experiment and try to build the different things they see on YouTube. They wanted multiple worlds to use as playgrounds for their different ideas.
And they even got me to play a few times.
So during the summer of 2020, I started looking into how I could build Minecraft server appliances. I had built a few Minecraft servers by hand before that, but they were difficult to maintain and keep up-to-date with Minecraft Server dot releases and general operating system maintenance.
I thought a virtual appliance would be the best way to do this, and this is my opinionated way of building a Minecraft server.
A little bit of history
The initial version of the virtual appliance was built on Photon. Photon is a stripped down version of Linux created by my employer for virtual appliances and running container workloads. William Lam has some great content on how to create a Photon-based virtual appliance using Packer.
This setup worked pretty well until Minecraft released version 1.17, also known as the Caves and Cliffs version, in the summer of 2021.
There are a couple of versions of Minecraft. The two main ones are Bedrock, which is geared towards Windows, mobile devices, and video game consoles, and Java, which uses Java and only runs on Windows, Linux, and Mac.
My kids play Java edition, and up until this point, Minecraft Java edition servers used the Java8 JDK. Minecraft 1.17, however, required the Java16 JDK. And that led to a second problem. The only JDK in the Photon repositories at the time was for Java8.
Now this doesn’t seem like a problem, or at least it isn’t on a small scale. There are a few open-source OpenJDK implementations that I could adopt. I ended up going with Adoptium’s Temurin OpenJDK. But after building a server or two, I didn’t really feel like maintaining a manual install process. I wanted the ease of use that came with a installing and updating from a package repository, and that wasn’t available for Photon.
So I needed a different Linux distribution. CentOS would have been my first choice, but I didn’t want something that was basically a rolling release candidate. My colleague Timo Sugliani spoke very highly of Debian, and he released a set of Packer templates for building lightweight Debian virtual appliances on GitHub. I modified these templates to use the Packer vSphere-ISO plugin and started porting over my appliance build process.
Customizing the Minecraft Experience
Do you want a flat world or something without mob spawns? Or try out a custom world seed? You can set that during the appliance deployment. I wanted the appliance to be self-configuring so I spent some time extending William Lam’s OVF properties XML file to include all of the Minecraft server attributes that you can configure in the Server.Properties file. This allows you to deploy the appliance and configure the Minecraft environment without having to SSH into it to manually edit the file.
One day, I may trust my kids enough to give them limited access to vCenter to deploy their own servers. This would make it easier for them.
Unfortunately, that day is not today. But this still makes my life easier.
Installing and Configuring Minecraft
The OVF file does not contain the Minecraft Server binaries. It actually gets installed during the appliance’s first boot. There are few reasons for this. First, the Minecraft EULA does not allow you to distribute the binaries. At least that was my understanding of it.
Second, and more importantly, you may not always want the latest and greatest server version, especially if you’re planning to develop or use mods. Mods are often developed against specific Minecraft versions, and they have a lengthy interoperability chart.
The appliance is not built to utilize mods out of the box, but there is nothing stopping someone from installing Forge, Fabric, or other modified binaries. I just don’t feel like taking on that level of effort, and my kids have so far resisted learning important life skills like the Bash CLI.
And finally, there isn’t much difference between downloading and installing the server binary on first boot and downloading and installing an updated binary. Minecraft Java edition is distributed as a JAR file, so I only really need to download it and place it in the correct folder.
I have a pair of PowerShell scripts that make these processes pretty easy. Both scripts have the same core function – query an online version manifest that is used by the Minecraft client and download the specified version to the local machine. The update script also has some extra logic in it to check if the service is running and gracefully stop it before downloading the updated server.jar file.
You can find these scripts in the files directory on GitHub.
Running Minecraft as a systemd Service
Finally, I didn’t want to have to deal with manually starting or restarting the Minecraft service. So I Googled around, and I found a bunch of systemd sample files. I did a lot of testing with these samples (and I apologize, I did not keep track of the links I used when creating my service file) to cobble together one of my own.
My service file has an external dependency. The MCRCON tool is required to shut down the service. While I was testing this, I ran into a number of issues where I could stop Minecraft, but it wouldn’t kill the Java process that spawned with it. It also didn’t guarantee that the world was properly saved or that users were alerted to the shutdown.
By using MCRCON, we can alert users to the shutdown, save the world, and gracefully exit all of the processes through a server shutdown command.
I also have the Minecraft service set to restart on failure. My kids have a tendency to crash the server by blowing up large stacks of TNT in a cave or other crazy things they see on YouTube, and that tends to crash the binary. This saves me a little headache by restarting the process.
Before we begin, you’ll want to have a couple of prerequisites. These are:
- The latest copy of HashiCorp’s Packer tool installed on your build machine
- The latest copy of the Debian 11 NetInstall ISO
There are a couple of files that you should edit to match your environment before you attempt the build process these are:
- Debian.auto.pkrvars.hcl – variables for the build process
- debian-minecraft.pkr.hcl file – the iso_paths line includes part of a hard-coded path that may not reflect your environment, and you may want to change the CPUs or RAM allocated to the VM.
- Preseed.cfg file located in the HTTP folder: localization information and root password
This build process uses the Packer vsphere-iso build process, so it talks to vCenter. It does not use the older vmware-iso build process.
The Appliance Build Process
As I mentioned above, I use Packer to orchestrate this build process. There is a Linux shell script in the public GitHub repo called build.sh that will kick off this build process.
The first step, obviously, is to install Debian. This step is fully automated and controlled by the preseed.cfg file that is referenced in the packer file.
Once Debian is installed, we copy over a default Bash configuration and our init-script that will run when the appliance boots for the first time to configure the hostname and networking stack.
After these are files are copied over, the Packer build begins to configure the appliance. The steps that it takes are:
- Run an apt-get update & apt-get upgrade to upgrade any outdated installed packages
- Install our system utilities, including UFW
- Configure UFW to allow SSH and enable it
- Install VMware Tools
- Set up the Repos for and install PowerShell and the Temurin OpenJDK
- Configure the rc.local file that runs on first boot
- Disable IPv6 because Java will default to communicating over IPv6 if it is enabled
After this, we do our basic Minecraft setup. This step does the following:
- creates our Minecraft service user and group
- sets up our basic folder structure in /opt/Minecraft
- downloads MCRCON into the /opt/Minecraft/tools/mcrcon directory.
- Copy over the service file and scripts that will run on first boot
The last three steps of the build are to run a cleanup script, export the appliance to OVF, and create the OVA file with the configurable OVF properties. The cleanup script cleans out the local apt cache and log files and zeroes out the free space to reduce the size of the disks on export.
The configurable OVF properties include all of the networking settings, the root password and SSH key, and, as mentioned above, the configurable options in the Minecraft server.properties file. OVFTool and William Lam’s script are required to create the OVA file and inject the OVF properties, and the process is outlined in this blog post.
The XML file with the OVF Properties is located in the postprocess-ova-properties folder in my GitHub repo.
The outcome of this process is a ready-to-deploy OVA file that can be uploaded to a content library.
So what happens after you deploy the appliance and boot it for the first time.
First, the debian-init.py script will run to configure the basic system identity. This includes the IP address and network settings, root password, and SSH public key for passwordless login.
Second, we will regenerate the host SSH keys so each appliance will have a unique key. If we don’t do this step, every appliance we deploy will have the same SSH host keys as the original template. This is handled by the debian-regeneratesshkeys.sh script that is based on various scripts that I found on other sites.
Our third step is to install and configure the Minecraft server using the debian-minecraftinstall.sh script. This has a couple of sub-steps. These are:
- Retrieve our Minecraft-specific OVF Properties
- Call our PowerShell script to download the correct Minecraft server version to /opt/Minecraft/bin
- Initialize the Minecraft server to create all of the required folders and files
- Edit eula.txt to accept the EULA. The server will not run and let users connect without this step
- Edit the server.properties file and replace any default values with the OVFProperties values
- Edit the systemd file and configure the firewall to use the Minecraft and RCON ports
- Reset permissions and ownership on the /opt/Minecraft folders
- Enable and start Minecraft
- Configure our Cron job to automatically install system and Minecraft service updates
The end result is a ready-to-play Minecraft VM.
All of the Packer files and scripts are available in my GitHub repository. Feel free to check it out and adapt it to your needs.