So I may have fallen a bit into Minecraft, which I'm sure is not really out there in terms of abhorrent behavior given the game's popularity. The more interesting bit is that I decided to take my game world and stuff it into a server so I could play it on any of my computers (I have a MacBook Pro laptop and a Windows 10 PC, amongst others) without having to deal with wacky file sync (there is a HOW-TO on using something like Dropbox to share your world but that looks like an absolutely awful idea to me), locking and potential corruption issues.
Before we backup, we need to setup.
There are a number of options as far as servers go but I decided to use the vanilla server. I also wanted it to be fast when I'm home but also available when I'm not at home. The VPN solves the second question and locating it on my Docker server at home solves the first. The process looked like this:
- Create docker-compose YAML file.
- Create Puppet manifest to deploy.
- Copy
~/Library/Application Support/minecraft/saves/My_World
to the location I mounted as /data into the container. - Setup SRV record on internal DNS servers.
- Add server to client and enjoy.
I'm not going to go through all of those steps, the guide on the official wiki is quite good. I'm using the itzg/minecraft-server image that they recommend as after looking at it, nothing made me want to vomit.
The docker-compose file I came up with is as follows:
version: '3'
services:
minecraft:
image: registry.hub.docker.com/itzg/minecraft-server:latest
ports:
- "25565:25565"
volumes:
- /var/local/docker/data/minecraft:/data
environment:
EULA: "TRUE"
TZ: "America/New_York"
restart: always
You almost certainly will need to change the volume location to something appropriate for your installation, but as you can see it is quite simple. The documentation for the container image contains several other examples and is quite robust.
I deployed all the bits and pieces and connected and spent the next week or so happily building away.
Now we have data we care about, so we must back it up.
Having a complex environment, I already have a fairly robust backup
strategy setup. The short version is that I have a volume on a NAS
in three locations called /vol/backup
and it is copied to a different
location every day using rdiff-backup(1). All I need to do to ensure
I have good off-site backups is to plop a copy of whatever data I want
in that volume on my local NAS.
I came up with the following script that cron(8) runs at 4:45am every day, it gracefully warns any logged in users every minute for a configurable number of minutes before doing the backup. Comments in-line explain most of what is going on.
#!/bin/sh
# backup-minecraft (c) 2020 Matthew J Ernisse <matt@going-flying.com>
# All Rights Reserved.
#
# Redistribution and use in source and binary forms,
# with or without modification, are permitted provided
# that the following conditions are met:
#
# * Redistributions of source code must retain the
# above copyright notice, this list of conditions
# and the following disclaimer.
# * Redistributions in binary form must reproduce
# the above copyright notice, this list of conditions
# and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set -e
# Variables used by backup-functions
BACKUP_DIR="/vol/backup/minecraft"
BACKUP_FILE="world"
KEEP_DAYS=7
COMPOSEFILE="[ PATH AND FILENAME TO YOUR docker-compose.yml ]"
CONTAINERNAME="[ NAME OF YOUR SERVER CONTAINER ]"
RCON_PASSWORD="[ SET IN YOUR server.properties FILE ]"
RESTART=0
WAIT_MINS=[ NUMBER OF MINUTES TO WARN USERS FOR ]
WORLDDIR="[ YOUR WORLD FOLDER ]"
. /usr/local/lib/backup-functions
# Check if the container is running, if it is we warn users to logoff
# before shutting down for backup. Note that we ALWAYS start the container
# up when we are done.
if ! docker ps --format '{{ .Names }}' | grep -q $CONTAINERNAME; then
echo "Minecraft container not running!"
else
RESTART=1
fi
# If we found the container running we want to warn users, so loop on
# WAIT_MINS, using rcon-cli inside the minecraft-server container to
# warn any logged in users.
if [ $RESTART -eq 1 ]; then
while [ $WAIT_MINS -gt 0 ]; do
docker exec $CONTAINERNAME rcon-cli \
--password $RCON_PASSWORD \
say \
"Server going down for backup in $WAIT_MINS minutes. Please logoff."
WAIT_MINS=$((WAIT_MINS - 1))
sleep 60
done
docker exec $CONTAINERNAME rcon-cli \
--password $RCON_PASSWORD \
say \
"Server going down for backup NOW!"
docker-compose --no-ansi -f $COMPOSEFILE down
fi
# backup_and_rotate is provided by my /usr/local/lib/backup-functions
# shell fragment that I include above. See later in the post for information
# about how it works.
backup_and_rotate "$WORLDDIR"
# Finally, restart (or start) the container.
docker-compose --no-ansi -f $COMPOSEFILE up -d
The above script includes some boilerplate that I use in about 20 different
application backup scripts. It takes a directory and makes a compressed
archive of it in a directory given via the BACKUP_DIR
variable. It then
looks at the directory and deletes any backups older than KEEP_DAYS
but
only if there is more than 1 backup. It is pretty basic find(1), and
tar(1). If you are interested, drop me an e-mail and if enough people
reach out perhaps I'll write a follow-up post going through it.
Closing Thoughts
The general consensus is that running a large scale Minecraft server is hard but if you are looking to run a small server for yourself and up to a small handful of people you know the Docker container route is a really good solution. It is even available for the Raspberry Pi if you are so inclined. If you are going to run a small server it seems silly to rely on multi-hundred line shell scripts downloaded from GitHub that cater to the larger, multi-world servers. As I have hopefully shown it isn't really all that difficult to create a graceful backup of your world data.