Spigot Minecraft Server auf Docker mit Docker-Compose: Wir containerisieren eine (Java) Anwendung (X86 Server und Raspberry Pi)

Als Video ansehen
Bereitgestellt über YouTube

Spigot Minecraft Server auf Docker mit Docker-Compose: Wir containerisieren eine (Java) Anwendung (X86 Server und Raspberry Pi)

In einem vorherigen Beitrag haben wir bereits einen Minecraft-Server nativ auf dem Raspberry Pi installiert – dies funktioniert auch auf einem X86 Server mit Debian. Dieser Beitrag erklärt, wie man Spigot mit Docker und Docker Compose containerisiert. Aus lizenztechnischen Gründen dürfen keine fertig erstellten Jar-Dateien bereitgestellt werden, weswegen fertige Images problematisch sind. Doch das ist mit Docker kein Problem: Wir nutzen dies als praktisches Beispiel, um eine Java-Anwendung selbst mit eigens erstelltem Dockerfile zu containerisieren. Die grundsätzlichen Vorteile von Containern/Docker habe ich in diesem Beitrag bereits aufgezeigt.

Das brauchst du

  • Einen Raspberry Pi 4 oder X86 VM/Server mit Debian oder Raspberry Pi OS
  • Docker mit installiertem Docker Compose Plugin
  • Hardwaretechnisch sollten es mindestens 4 Prozessorkerne mit 2 GB Arbeitsspeicher sein – wobei das eher unten angesiedelt ist, besser sind 4 GB RAM oder mehr.
  • Port 25565 muss erreichbar sein (ggf. in der Firewall freischalten, falls eine verwendet wird, z.B. beim Hoster)
  • Zum Testen (und späteren Spielen) einen PC/Laptop mit entsprechender Minecraft Client-Lizenz (der Server benötigt keine Lizenz, nur die Clients)

#1 Welche Basis-Image?

Theoretisch kann man sein eigenes Docker-Image von null auf selbst erstellen, d.H. jegliche benötigte Software gezielt installieren. Dieser minimalistische Ansatz ist allerdings aufwändiger – sowohl zu Beginn als auch in der späteren Wartung. Es macht mehr Sinn, nach einem vertrauenswürdigen Basis-Image zu suchen, dass die gewünschten Komponenten bereits enthält. Für Minecraft ist dies eine freie Java-Implementierung, etwa von OpenJDK. Diese wurde kürzlich Teil von Eclipse und stellt offizielle Images unter eclipse-temurin bereit.

Spigot benötigt für 1.18.x derzeit mindestens Java 17 LTS, für neuere Versionen sollten die Anforderungen in der Dokumentation geprüft werden. Wir benötigen nicht nur die Laufzeitumgebung JRE, sondern zum Erstellen der Jar-Datei auch das Developer Kit (JDK). Der Tag 17-jdk-jammy ist für x86 in der 64-Bit Edition eben so wie ARM v7/8 und damit auch dem Raspberry verfügbar. Etwas effizienter wäre 17-jdk-alpine, er basiert auf dem schlanken Alpine Linux. Hier könnte es eventuell zu Problemen kommen, da Alpine musk statt libc verwendet. In beiden Fällen haben wir Java mit den Entwicklertools und damit alles, was wir an Abhängigkeiten für Minecraft benötigen.

#2 Das Dockerfile

Beim Containerisieren einer Anwendung halten wir uns an deren Anforderungen. Im Kern wird dabei nichts anderes gemacht, wie bei einer nativen Installation. Als generelle Checkliste kann man sich folgende Schritte vormerken:

  1. Auswahl des Basis-Images mit passendem Versions-Tag
  2. Installation von zusätzlichen Abhängigkeiten (falls notwendig)
  3. Setzen von bestimmten Einstellungen/Parametern, die von der Anwendung benötigt werden
  4. Anlegen eines Benutzers, damit das Programm nicht als root mit privilegierten Rechten läuft inklusive dem setzen der notwendigen Rechte
  5. Aufräumen (z.B. temporäre Dateien entfernen)
  6. Erstellung eines Einstiegspunkt, der die Anwendung startet

Konkret brauchen wir in Schritt #2 git und wget für Spigot. Das Paket gosu dient zum Wechsel des Benutzers, dazu später mehr. Anschließend wird sichergestellt, dass die Git-Einstellung core.autocrlf nicht gesetzt ist, wie in den Anforderungen dokumentiert. Zur eigentlichen Installation laden wir uns die BuildTools, erstellen daraus die Jar-Datei des Minecraft-Servers, setzen passende Rechte für den Nutzer, räumen auf und starten Spigot. Für alles was man ggf. anpassen möchte, kommen Argumente zum Einsatz. So kann man etwa MC_VERSION von 1.18.2 auf 1.19.0 setzen, um die derzeit neueste Version zu nutzen.

FROM eclipse-temurin:17-jdk-jammy
# Infos zu neuen Versionen https://www.spigotmc.org/
ARG MC_VERSION=1.18.2
ARG MEM_LIMIT=1G
ARG USER=minecraft-docker
ARG GROUP=minecraft-docker
ARG UID=2000
ARG GID=2000
ARG PLUGIN_DIR=/plugins
ARG WORLD_DIR=/world
ARG EULA=true
ENV MC_VERSION=$MC_VERSION
ENV USER=$USER
ENV GROUP=$GROUP
ENV MEM_LIMIT=$MEM_LIMIT
ENV PLUGIN_DIR=$PLUGIN_DIR
ENV WORLD_DIR=$WORLD_DIR

RUN apt-get update \
    && apt-get install -y git wget gosu

# Git liefert den Exitcode 5, wenn der Parameter in der Konfiguration nicht gesetzt ist (Erkennt Docker als fehlerhaft)
# Wird nicht benoetigt, da im Standard unter Ubuntu 22 LTS nicht gesetzt
#RUN git config --global --unset 'core.autocrlf' || exit 0

VOLUME ${WORLD_DIR}
VOLUME ${PLUGIN_DIR}
WORKDIR /minecraft
RUN mkdir -p ${WORLD_DIR} ${PLUGIN_DIR}
# Die Eula-Datei wird erst beim ersten Start angelegt, sofern nicht bereits eine mit eula=true existiert
RUN wget https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar \
    && java -jar BuildTools.jar --rev $MC_VERSION \
    && echo "eula=${EULA}" > eula.txt

RUN apt-get remove -y git wget \
    && rm -rf /var/lib/apt/lists/*

RUN groupadd -g ${GID} ${GROUP} \
    && useradd -u ${UID} -g ${GROUP} -s /bin/sh -m ${USER} \
    && chown ${USER}:${GROUP} . -R \
    && chown ${USER}:${GROUP} ${WORLD_DIR} -R \
    && chown ${USER}:${GROUP} ${PLUGIN_DIR} -R
#USER ${UID}:${GID}

COPY docker-entrypoint.sh .
RUN chmod +x ./docker-entrypoint.sh

# CMD wird nicht genutzt, da in der Shell-Form ein Kindprozess gestartet wird -> Signale werden nicht weitergereicht
# Exec-Form loest Variablen nicht auf, die wir z.B. fuer das RAM Limit brauchen
ENTRYPOINT ["./docker-entrypoint.sh"]

Die beiden Volumes sind zum einen für die Minecraft Welt – sie enthält alle Daten zum Spielstand eures Servers und müssen daher unbedingt persistent in einem eigenen Volume gespeichert werden. Ansonsten würdet ihr mit einem frischen Server ohne eigene Inhalte starten, sobald der Container neu erstellt wird. Über das Plugins-Volume kann man eigene Plugins installiern – beispielsweise LuckPerms zur Rechteverwaltung.

Der Einstiegspunkt (ENTRYPOINT) führt das Skript docker-entrypoint.sh beim Start des Containers aus. Dies müssen wir noch erstellen. Es soll sicherstellen, dass die Berechtigungen beim Start korrekt sind – beispielsweise, wenn ein Plugin in das dazugehörige Volume verschoben wird. Außerdem starten wir hier den Server mit unserem Benutzer. Theoretisch könnte man dies auch mit der USER Direktive im Dockerfile festlegen. Das Problem: Die Berechtigungen würden sich dann nicht setzen lassen, da unser Nutzer hierzu nicht berechtigt ist. Daher wird das Skript als root ausgeführt und wir wechseln mit gosu für den Programmstart auf einen weniger privilegierten Benutzer:

#!/bin/bash
chown $USER:$GROUP $PLUGIN_DIR -R
echo "Verfügbare Plugins in ${PLUGIN_DIR}"
ls -lha $PLUGIN_DIR

# https://www.spigotmc.org/wiki/start-up-parameters
exec gosu $USER:$GROUP java -Xms${MEM_LIMIT} -Xmx${MEM_LIMIT} -XX:+UseG1GC -jar spigot-${MC_VERSION}.jar nogui --world-dir ${WORLD_DIR} --plugins ${PLUGIN_DIR} --bukkit-settings ${WORLD_DIR}/bukkit.yml --commands-settings ${WORLD_DIR}/commands.yml --config ${WORLD_DIR}/server.properties --spigot-settings ${WORLD_DIR}/spigot.yml

Um aus dieser Dockerfile ein Image erstellen und starten zu können, erstellen wir eine docker-compose.yml im gleichen Ordner:

services:
  spigot:
    build: .
    container_name: spigot
    mem_limit: 1.5G
    stdin_open: true
    tty: true
    ports:
      - 25565:25565
    volumes:
      - world:/world
      - plugins:/plugins

volumes:
  world:
  plugins:

Je nachdem wie viel Arbeitsspeicher zur Verfügung steht, sollte dies entsprechend angepasst werden – auch im Dockerfile.

Starten und Minecraft-Konsolenbefehle ausführen

Zu empfehlen ist, den Container mit dem Argument –build zu starten. Dadurch prüft Compose, ob das Dockerfile verändert wurde und baut den Container neu. Beim ersten Mal geschieht dies automatisch, da noch kein Image vorhanden ist. Bei späteren Änderungen (z.B. Upgrade auf eine neue Minecraft-Version)

docker compose up --build -d

Um Konsolenbefehle auszuführen, verwendet nach dem vollständigen Start des Servers attach:

docker attach spigot

Anschließend die gewünschten Befehle (z.B. help) eingeben, der Befehl wird ganz normal ausgeführt. Mit [STRG] + [P] gefolgt von [STRG] + [Q] schließt ihr die Konsole, ohne den Minecraft-Server zu beenden.

Plugins installieren

Damit haben wir einen Vanilla-Minecraftserver auf Basis von Spigot. Wer diesen mit Erweiterungen anpassen möchte, kann dafür das zuvor bereits erstellte Volume verwenden. Volumes liegen im gleichnamigen Unterordner. Über docker info bekommt man das Docker-Wurzelverzeichnis heraus, meist /var/lib/docker. Mit grep auf den Name des Ordners (minecraft-docker) filtern, da dieser bei Compose als Präfix für die Volumes dient. Anschließend kann man die gewünschten Daten (z.B. Jar-Datei eines Plugins) in dessen Unterordner _data verschieben:

$ docker info | grep "Root Dir"
 Docker Root Dir: /var/lib/docker

$ sudo ls -lh /var/lib/docker/volumes | grep minecraft
drwx-----x 3 root root 4,0K Jun 20 23:38 minecraft-docker_plugins
drwx-----x 3 root root 4,0K Jun 20 22:03 minecraft-docker_world

$ wget https://download.luckperms.net/1438/bukkit/loader/LuckPerms-Bukkit-5.4.30.jar
$ sudo mv LuckPerms-Bukkit-5.4.30.jar /var/lib/docker/volumes/minecraft-docker_plugins/_data
$ docker compose up --build -d; docker compose logs -f

Im Protokoll des Servers, welches mit docker-compose logs -f aufgerufen wird, solltet ihr sehen können, dass euer Plugin geladen wird:

[09:44:30] [Server thread/INFO]: [LuckPerms] Loading LuckPerms v5.4.30
[09:44:33] [Server thread/INFO]: [LuckPerms] Enabling LuckPerms v5.4.30
[09:44:33] [Server thread/INFO]:         __    
[09:44:33] [Server thread/INFO]:   |    |__)   LuckPerms v5.4.30
[09:44:33] [Server thread/INFO]:   |___ |      Running on Bukkit - CraftBukkit

Upgrades auf neuere Minecraft-Versionen durchführen

Im Dockerfile habe ich für die Version ein Argument namens MC_VERSION festgelegt. Es steht bewusst auf Version 1.18.2, obwohl beim Erstellzeitpunkt dieses Artikels bereits 1.19 verfügbar ist. So können wir ein Upgrade testen. Wie grundsätzlich vor einem Upgrade solltet ihr den Server herunterfahren und die Daten sichern – ein Downgrade wird von Minecraft nicht unterstützt.

# Falls noch nicht installiert
$ sudo apt install zip

$ docker compose down
$ sudo zip -r minecraft-world-backup-23.06.2022.zip /var/lib/docker/volumes/minecraft-docker_world
$ sudo zip -r minecraft-plugins-backup-23.06.2022.zip /var/lib/docker/volumes/minecraft-docker_plugins

Die Version im Dockerfile auf die aktuellste/gewünschte erhöhen, etwa 1.19. Am besten vorher auf die Homepage von Spigot nach der neuesten schauen.

ARG MC_VERSION=1.19

Anschließend das Image bauen und den Container neu erstellen. Dies kann man in einzelnen Schritten machen (docker compose build und docker compose up) oder in letzterem Befehl mit dem Parameter –build vereinen:

$ docker compose up -d --build
$ docker compose logs -f

Abhängig von der Leistung eures Servers wird dieser Vorgang einige Zeit dauern. Es werden die aktuellsten Build-Tools heruntergeladen, die eine neue JAR-Datei in der gewünschten Version erstellen. Danach erstellt Compose mit dem aktualisierten Image einen neuen Container. An dieser Stelle wäre es zudem sinnvoll, die Plugins auf Aktualität zu prüfen, falls ihr welche installiert habt. Sobald der Server in den Protokollen den vollständigen Start mitteilt (Done), kann man sich mit einem Client verbinden. Vergesst vorher nicht, die neue Version im Client auszuwählen!

Im Spiel kann man mit F3 die Version einsehen:

Auch die Serverkonsole (docker compose logs -f) zeigt diese an. Allerdings muss man hier etwas suchen:

...
[10:42:13] [Server thread/INFO]: Starting minecraft server version 1.19
...

Leave a Reply