add: add Docker support for Dedicated Server

add with entrypoint and build scripts
This commit is contained in:
kuwacom 2026-03-06 21:38:43 +09:00
parent 392dfdad1f
commit 06bff53e4d
7 changed files with 271 additions and 0 deletions

1
.gitignore vendored
View file

@ -442,3 +442,4 @@ Minecraft.Client/Saves/
tmp*/
_server_asset_probe/
server-data/

View file

@ -58,6 +58,51 @@ Minecraft.Client.exe -server -ip 0.0.0.0 -port 25565
The headless server also reads and writes `server.properties` in the working directory. If `-ip` / `-port` are omitted in `-server` mode, it falls back to `server-ip` / `server-port` from that file. Dedicated-server host options such as `trust-players`, `pvp`, `fire-spreads`, `tnt`, `difficulty`, `gamemode`, `spawn-animals`, and `spawn-npcs` are persisted there as well.
## Dedicated Server in Docker (Wine)
This repository includes a lightweight Docker setup for running the Windows dedicated server under Wine.
**To build the image, a release build of `Minecraft.Server` is required.**
1. Build the dedicated server (`Minecraft.Server`) first.
2. Start with Docker Compose (server runtime files are copied into the image at build time):
```bash
docker compose -f docker-compose.dedicated-server.yml up -d
```
or use the helper script:
```bash
./start-dedicated-server.sh
```
You can explicitly choose runtime:
**However, debug builds often fail to run because some debug-related libraries are missing.**
```bash
./start-dedicated-server.sh release
./start-dedicated-server.sh debug
```
By default, build input is `x64/Minecraft.Server/Release`. Override it when needed (path must be inside this repository):
```bash
MC_RUNTIME_DIR=x64/Minecraft.Server/Debug docker compose -f docker-compose.dedicated-server.yml up -d --build
```
Useful environment variables:
- `XVFB_DISPLAY` (default: `:99`)
- `XVFB_SCREEN` (default: `64x64x16`, tiny virtual display used by Wine)
Fixed server runtime behavior in container:
- executable path: `/srv/mc/Minecraft.Server.exe`
- bind IP: `0.0.0.0`
- server port: `25565`
Persistent files are bind-mounted to host:
- `./server-data/server.properties` -> `/srv/mc/server.properties`
- `./server-data/GameHDD` -> `/srv/mc/Windows64/GameHDD`
## Controls (Keyboard & Mouse)
- **Movement**: `W` `A` `S` `D`

View file

@ -0,0 +1 @@
docker compose -f docker-compose.dedicated-server.yml build

View file

@ -0,0 +1,27 @@
services:
minecraft-lce-dedicated-server:
build:
context: .
dockerfile: docker/dedicated-server/Dockerfile
args:
MC_RUNTIME_DIR: ${MC_RUNTIME_DIR:-x64/Minecraft.Server/Release}
container_name: minecraft-lce-dedicated-server
restart: unless-stopped
environment:
TZ: ${TZ:-Asia/Tokyo}
WINEARCH: win64
WINEPREFIX: /var/opt/wineprefix64
WINEDEBUG: -all
# minimum required virtual screen
XVFB_DISPLAY: ${XVFB_DISPLAY:-:99}
XVFB_SCREEN: ${XVFB_SCREEN:-64x64x16}
volumes:
# - wineprefix64:/var/opt/wineprefix64
- ./server-data:/srv/persist
ports:
- "25565:25565/tcp"
- "25565:25565/udp"
stop_grace_period: 30s
# volumes:
# wineprefix64:

View file

@ -0,0 +1,33 @@
FROM debian:bookworm-slim
ARG DEBIAN_FRONTEND=noninteractive
# basically, it only works with a Release build.(libs are not included in Debug build)
ARG MC_RUNTIME_DIR=x64/Minecraft.Server/Release
RUN dpkg --add-architecture i386 \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
wine \
wine64 \
wine32:i386 \
xvfb \
tini \
&& rm -rf /var/lib/apt/lists/*
ENV WINEARCH=win64
ENV WINEPREFIX=/var/opt/wineprefix64
WORKDIR /srv/mc
COPY ${MC_RUNTIME_DIR}/Minecraft.Server.exe /srv/mc/Minecraft.Server.exe
COPY ${MC_RUNTIME_DIR}/iggy_w64.dll /srv/mc/iggy_w64.dll
COPY ${MC_RUNTIME_DIR}/Common /srv/mc/Common
COPY ${MC_RUNTIME_DIR}/Windows64 /srv/mc/Windows64
COPY docker/dedicated-server/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod 0755 /usr/local/bin/entrypoint.sh \
&& mkdir -p /var/opt/wineprefix64 /srv/mc \
&& test -f /srv/mc/Minecraft.Server.exe
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"]

View file

@ -0,0 +1,74 @@
#!/usr/bin/env bash
set -euo pipefail
SERVER_DIR="/srv/mc"
SERVER_EXE="Minecraft.Server.exe"
# ip & port are fixed since they run inside the container
SERVER_PORT="25565"
SERVER_BIND_IP="0.0.0.0"
PERSIST_DIR="/srv/persist"
WINE_CMD=""
if [ ! -d "$SERVER_DIR" ]; then
echo "[error] Server directory not found: $SERVER_DIR" >&2
exit 1
fi
cd "$SERVER_DIR"
if [ ! -f "$SERVER_EXE" ]; then
echo "[error] ${SERVER_EXE} not found in ${SERVER_DIR}" >&2
echo "[hint] Rebuild image with a valid MC_RUNTIME_DIR build arg that contains dedicated server runtime files." >&2
exit 1
fi
# created because it is not implemented on the server side
mkdir -p "${PERSIST_DIR}/GameHDD"
if [ ! -f "${PERSIST_DIR}/server.properties" ]; then
if [ -f "server.properties" ] && [ ! -L "server.properties" ]; then
cp -f "server.properties" "${PERSIST_DIR}/server.properties"
else
: > "${PERSIST_DIR}/server.properties" # the server writes the default configuration if the file is empty, so it works
fi
fi
# differs from the structure, but its reorganized into a more manageable structure to the host side
if [ -e "Windows64/GameHDD" ] && [ ! -L "Windows64/GameHDD" ]; then
rm -rf "Windows64/GameHDD"
fi
ln -sfn "${PERSIST_DIR}/GameHDD" "Windows64/GameHDD"
ln -sfn "${PERSIST_DIR}/server.properties" "server.properties"
# for compatibility with other images
if command -v wine64 >/dev/null 2>&1; then
WINE_CMD="wine64"
elif [ -x "/usr/lib/wine/wine64" ]; then
WINE_CMD="/usr/lib/wine/wine64"
elif command -v wine >/dev/null 2>&1; then
WINE_CMD="wine"
else
echo "[error] No Wine executable found (wine64/wine)." >&2
exit 1
fi
if [ ! -d "${WINEPREFIX}" ] || [ -z "$(ls -A "${WINEPREFIX}" 2>/dev/null)" ]; then
mkdir -p "${WINEPREFIX}"
fi
# in the current implementation, a virtual screen is required because the client-side logic is being called for compatibility
if [ -z "${DISPLAY:-}" ]; then
export DISPLAY="${XVFB_DISPLAY:-:99}"
XVFB_SCREEN="${XVFB_SCREEN:-64x64x16}"
Xvfb "${DISPLAY}" -nolisten tcp -screen 0 "${XVFB_SCREEN}" >/tmp/xvfb.log 2>&1 &
sleep 0.2
fi
args=(
-port "${SERVER_PORT}"
-bind "${SERVER_BIND_IP}"
)
echo "[info] Starting ${SERVER_EXE} on ${SERVER_BIND_IP}:${SERVER_PORT}"
exec "${WINE_CMD}" "${SERVER_EXE}" "${args[@]}"

90
start-dedicated-server.sh Normal file
View file

@ -0,0 +1,90 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
COMPOSE_FILE="${SCRIPT_DIR}/docker-compose.dedicated-server.yml"
SERVICE_NAME="minecraft-lce-dedicated-server"
PERSIST_DIR="${SCRIPT_DIR}/mc-server-data"
if [[ ! -f "${COMPOSE_FILE}" ]]; then
echo "[error] docker-compose file not found: ${COMPOSE_FILE}" >&2
exit 1
fi
if command -v docker >/dev/null 2>&1; then
COMPOSE_CMD=(docker compose)
elif command -v docker-compose >/dev/null 2>&1; then
COMPOSE_CMD=(docker-compose)
else
echo "[error] docker compose is not available." >&2
exit 1
fi
# can choose between release and debug, but honestly there isnt much use
if [[ "${#}" -gt 1 ]]; then
echo "Usage: $0 [release|debug|<runtime-dir-in-repo>]" >&2
exit 1
fi
mkdir -p "${PERSIST_DIR}/GameHDD"
if [[ ! -f "${PERSIST_DIR}/server.properties" ]]; then
: > "${PERSIST_DIR}/server.properties"
fi
if [[ "${#}" -eq 1 ]]; then
case "${1}" in
release)
MC_RUNTIME_DIR_INPUT="x64/Minecraft.Server/Release"
;;
debug)
MC_RUNTIME_DIR_INPUT="x64/Minecraft.Server/Debug"
;;
*)
MC_RUNTIME_DIR_INPUT="${1}"
;;
esac
else
if [[ -f "${SCRIPT_DIR}/x64/Minecraft.Server/Release/Minecraft.Server.exe" ]]; then
MC_RUNTIME_DIR_INPUT="x64/Minecraft.Server/Release"
else
MC_RUNTIME_DIR_INPUT="x64/Minecraft.Server/Debug"
fi
fi
if [[ "${MC_RUNTIME_DIR_INPUT}" = /* ]]; then
if [[ "${MC_RUNTIME_DIR_INPUT}" != "${SCRIPT_DIR}"/* ]]; then
echo "[error] runtime-dir must be inside repository: ${MC_RUNTIME_DIR_INPUT}" >&2
exit 1
fi
MC_RUNTIME_DIR_REL="${MC_RUNTIME_DIR_INPUT#${SCRIPT_DIR}/}"
else
MC_RUNTIME_DIR_REL="${MC_RUNTIME_DIR_INPUT#./}"
fi
if [[ -z "${MC_RUNTIME_DIR_REL}" || "${MC_RUNTIME_DIR_REL}" = "." ]]; then
echo "[error] runtime-dir is invalid: ${MC_RUNTIME_DIR_INPUT}" >&2
exit 1
fi
if ! RUNTIME_ABS="$(cd "${SCRIPT_DIR}" && cd "${MC_RUNTIME_DIR_REL}" 2>/dev/null && pwd)"; then
echo "[error] runtime-dir not found: ${MC_RUNTIME_DIR_INPUT}" >&2
exit 1
fi
if [[ "${RUNTIME_ABS}" != "${SCRIPT_DIR}"/* ]]; then
echo "[error] runtime-dir must resolve inside repository: ${MC_RUNTIME_DIR_INPUT}" >&2
exit 1
fi
MC_RUNTIME_DIR_REL="${RUNTIME_ABS#${SCRIPT_DIR}/}"
if [[ ! -f "${RUNTIME_ABS}/Minecraft.Server.exe" ]]; then
echo "[error] Minecraft.Server.exe not found in: ${RUNTIME_ABS}" >&2
echo "[hint] Build dedicated server first, then retry." >&2
exit 1
fi
echo "[info] Using runtime (build arg): ${MC_RUNTIME_DIR_REL}"
echo "[info] Persistent data dir: ${PERSIST_DIR}"
MC_RUNTIME_DIR="${MC_RUNTIME_DIR_REL}" "${COMPOSE_CMD[@]}" -f "${COMPOSE_FILE}" up -d --build "${SERVICE_NAME}"
echo "[info] Dedicated server started."