Addendum 08 September 2022: as of OpenBSD 7.0, later versions of OpenJDK are supported, which means you can now natively run the Minecraft server that this post was setting up. This was brought to my attention by Gabriel Guzman; thanks for the feedback!
“Thinking quickly, Dave constructs a homemade megaphone using only some string, a squirrel, and a megaphone.”1
Minecraft recently got its bump to version 1.18.1, and my girlfriend and I have been looking for a way to play without running the server locally on my machine. I have an OpenBSD box (see my previous post) that I have been using for my remote needs, so I figured I could run the server on it fairly easily.
The Hardware #
First, let’s meet the lambo I plan to have running this:
# top
...
CPU0 states: ...
CPU1 states: ...
Memory: Real: 82M/1782M act/tot Free: 6024M Cache: 1084M Swap: 0K/2818M
...
# df -h
Filesystem Size Used Avail Capacity Mounted on
/dev/ab0x 986M 107M 830M 11% /
...
/dev/ab0y 9.4G 936M 8.1G 10% /home
Oh hell yes. Two cores. Like 8G of RAM. Maybe 8G of disk space. We’re gonna make this thing work like Atlas.
The Software #
To get a Minecraft server running, all you need is the latest version of the JRE. For most machines, this really isn’t a problem. Java does run on 3 billion devices2, after all. How bad could it be? Let’s go ahead and check ports:
3 billion, it would seem, does not include OpenBSD. Well, not the latest version of Java, at least. OpenBSD doesn’t really have much in the way of modern luxury when it comes to Java. This is in line with their philosophy, I mean think about how many damn licenses are sitting on a JRE.
This said, I am not so headstrong as to port my own OpenJDK, not so eager to find a way to
use OpenPorts, and not so foolish as to use some weird hacky
stuff for running a jar
, so what’s the solution here?
Isn’t it so obvious? Just virtualize a Linux box!
Enter: vmm(4)
#
I decided to virtualize a lightweight Linux distribution inside of my OpenBSD box to run the latest server, then NAT Minecraft clients to it.
It’s easier to virtualize hardware on OpenBSD than you might think. vmm(4)
provides capability in spades. All we need to do to enable it is add a line to our
rc.conf.local(8)
.
echo "vmd_flags=" >> /etc/rc.conf.local
Let’s go ahead and create our working directory and its virtual disk with vmctl(8)
.
# useradd -m -s /sbin/nologin /home/minecraft
# cd /home/minecraft
# vmctl create -s 5G minecraft.qcow2
vmctl: qcow2 imagefile created
All we have to do now is just pick a Linux distribution to run on our (admittedly sparing) disk space. All I really need is Alpine, since it supports the latest JDK and keeps things tidy. After fetching the boot disk, the startup command isn’t so bad:
# vmctl start -m 2G -L -i 1 -r alpine-virt-x86_64.iso \
-d minecraft.qcow2 minecraft
vmctl: started vm 1 successfully, tty /dev/ttyp1
-m 2G
: 2G of memory.-L
: link over network. Basically spin up a subnet for the VM.-i 1
: one network interface.-r
: boot from an image.
All set.
# vmctl show
ID PID VCPUS MAXMEM CURMEM TTY OWNER STATE NAME
1 24772 1 2.0G 96.4M ttyp1 root running minecraft
I won’t lie, a 5G disk might be a bit pessimistic for how small we can get this thing, and 2G of memory might be pushing it for a machine that’s rocking the base amount that you’d expect out of a nicer Chromebook3. We’ll limit test later, though. Right now, there are more important matters to tend to.
NAT #
Before we can use our machine to reach the internet, we’ve got to redirect traffic to it.
Time for pf(4)
to go to work. We add a rule to redirect traffic in
on port 25565, and allow outbound traffic.
I don’t run my server as a DNS cache, so the third rule (pulled from xhr’s post) maps DNS to Cloudflare.
match out on egress from $minecraft to any \
nat-to egress
pass out on egress from $minecraft to any \
nat-to egress
pass in proto udp from $minecraft to any port \
domain rdr-to 1.1.1.1 port domain
pass in on egress proto tcp from any to (egress) \
port { 25565 } rdr-to $minecraft
pass in on egress proto udp from any to (egress) \
port { 25565 } rdr-to $minecraft
Before putting these rules into action, make sure that we have IP forwarding enabled in
sysctl(5)
:
# sysctl net.inet.ip.forwarding
net.inet.ip.forwarding=1
Networking is ready for our VM. Let’s get to work.
setup-alpine
#
Time to get our virtual machine set up. We can hop into the VM’s console, login as root
, and
run setup-alpine
as usual.
# vmctl console minecraft
Connected to /dev/ttyp1 (speed 115200)
Alpine’s install is streamlined like OpenBSD’s. Just page through with sane defaults and we’re set. Surprisingly, the arduous portion of this operation was just waiting for it to ping mirrors, start chrony, all that good stuff.
After that’s all set, we have to install dependencies. The only dependency we really need to run our Minecraft server is openjdk17.
Since this is available in community, we install it by specifying the repository:
apk add openjdk17 --repository=https://pkgs.alpinelinux.org/package/edge/community/aarch64/openjdk17
Once that’s done, we can check our version of the JRE.
# java --version
openjdk 17.0.2 2022-01-18
OpenJDK Runtime Environment (build 17.0.2+8-alpine-r0)
OpenJDK 64-Bit Server VM (build 17.0.2+8-alpine-r0, mixed mode, sharing)
Looking good. After downloading the latest Minecraft server jar
and prepping our server directory,
we just add the driver script for our preferred java
command as a start script in /etc/local.d/
.
On-Boot #
If we want to let this run automatically, we’re going to have to set up our VM to start on boot. This
is accomplished through vm.cfg(5)
.
vm "minecraft" {
memory 2G
enable
disk /home/minecraft/minecraft.qcow2
local interface
owner minecraft
}
Now when we reboot, the machine will run at boot and run under the minecraft
user.
Limit Testing #
Surprisingly, the entire Alpine + JDK install only comes out to about 345MiB. The size of the minecraft server installation on my Windows disk is about 391MB. So what we really require in disk space is about 3G. If we really wanted to push it, we could go to about 2G.
The amount of RAM the VM sips at idle is about ~300MB, so we could shove its RAM consumption down to about 1.5G.
athome# vmctl show minecraft
ID PID VCPUS MAXMEM CURMEM TTY OWNER STATE NAME
1 28555 1 1.5G 1.5G ttyp1 root running minecraft
…okay maybe we can’t. I went ahead and upped the memory to around 3G and that seemed to do the trick
for the stock server command that grants the jar
about 1G of RAM.
Performance #
And so comes the question: is it fast? Well, not really.
I tested it out and saw a pretty okay TPS, but it’s clear that a Minecraft server wasn’t meant to be run on a virtual machine with meager resources allocated to it on an already-resource-limited host. Notably, the whole server would eat dirt when transitioning between the Overworld and the Nether.
Once chunks were loaded, performance wasn’t an issue on the local network. Player activity was fairly snappy. This said, the chunk load time being painful made me uncomfortable taking this Frankenstein out of the lab. I just stuck with my own machine.
Conclusions and Future Considerations #
Running a Minecraft server on a virtualized Alpine machine isn’t a bad idea if you have the hardware for it. I do not.
In the future, I’d like to upgrade my server hardware and try this again, experimenting with ways of storing the Minecraft world folder in a host-system accessible folder. On any other virtualization solution, this wouldn’t be too hard, but OpenBSD’s virtualization daemon doesn’t support hardware passthrough yet4.
Network passthrough is a breeze with pf(4)
, but the only viable option
for file-sharing between the two is through a network share like smbd(8)
.
That would kill performance.
I think that if I were to get this set up, I’d just have a cronjob on the VM that periodically backs
up the Minecraft world at regular intervals over SSH to an S3 bucket with rclone(1)
.
#!/bin/sh
NAME="backup-$(date +%D).tar.gz"
tar -czf $NAME /root/server
rclone copy $NAME s3:/minecraftWorlds/
Anyways, this was a fun little project and an educative experience. I may return to it to see if I can improve performance on the little server that could™.