Why
A Minecraft server for friends usually means renting one, or opening a port on your router. This card runs Paper, a Java server, on a machine you own, with Geyser and Floodgate so friends on phones and consoles join the same world as friends on PC. The guide A game server on Edgible walks through the same server step by step.
What
One container, itzg/minecraft-server, on place minecraft, and two apps from it: mc-java for Java Edition over TCP on port 25565, and mc-bedrock for Bedrock Edition over UDP on port 29132, which Geyser answers and translates.
A TCP or UDP app has no auth mode: anyone who has the hostname can reach the port. So the game decides who plays. Online mode checks every Java player's account, the whitelist is on and enforced, and Geyser uses Floodgate, so Bedrock players sign in with their Xbox account and must be on Floodgate's whitelist. Both lists start empty, so no one gets in until you add them.
The server does not start until you accept the Minecraft EULA in card.env.
The world, the plugins and both whitelists live in the volume minecraft-data. The world grows as people explore, so give it room beyond the minimum. Minimum recommended for the place minecraft: 4 GB of memory and 2 GB of disk, on an arm64 or amd64 machine, with no GPU.
The Compose file is docker-compose.yml, the settings card.env, the card card.yml, and the last test test.yml.
How
Five steps. Edit card.env before you start.
1. Fetch
mkdir -p minecraft
curl -fsSL https://github.com/Edgible/cards/archive/refs/heads/main.tar.gz \
| tar -xz --strip-components=2 -C minecraft cards-main/minecraft
2. Edit card.env
Set DEVICE to a name edgible device list prints. Read the EULA and set MINECRAFT_EULA to TRUE. Change MINECRAFT_MOTD if you like; keep its quotes. Nothing here is secret.
nano minecraft/card.env
3. Check
curl -fsSLo check-env.py https://raw.githubusercontent.com/Edgible/card-kit/main/check-env.py
python3 check-env.py minecraft
It fills each empty secret and moves a taken port, as lines to paste. Run it again until it says no conflicts.
4. Start
docker compose --env-file minecraft/card.env -f minecraft/docker-compose.yml up -d --wait
docker compose --env-file minecraft/card.env -f minecraft/docker-compose.yml ps
--wait returns when each service is running, and healthy when it has a healthcheck. The first start downloads the server and both plugins, which takes a minute or two. Then switch Geyser to Floodgate sign-in, before Publish:
sh minecraft/prepare.sh
prepare.sh is:
#!/bin/sh
# Prepare for the minecraft card: switch Geyser to Floodgate sign-in, so Bedrock players sign in with Xbox.
# Run it after Start and before Publish, from the directory that holds minecraft/:
# sh minecraft/prepare.sh
set -eu
cd "$(dirname "$0")"
set -a; . ./card.env; set +a
COMPOSE=${COMPOSE:-docker compose --env-file card.env -f docker-compose.yml}
$COMPOSE exec -T minecraft \
sed -i 's/auth-type: online/auth-type: floodgate/' /data/plugins/Geyser-Spigot/config.yml
$COMPOSE restart minecraft
$COMPOSE up -d --wait
Then add the players, yourself too:
docker compose --env-file minecraft/card.env -f minecraft/docker-compose.yml exec minecraft rcon-cli whitelist add <java-name>
docker compose --env-file minecraft/card.env -f minecraft/docker-compose.yml exec minecraft rcon-cli fwhitelist add <gamertag>
rcon-cli sends a command to the running server, so adding a friend later needs no restart.
5. Publish
set -euo pipefail
set -a
. minecraft/card.env
set +a
device_id=$(edgible device list --json | jq -er --arg name "$DEVICE" '
map(select(.name == $name))
| if length == 1 then .[0].id
else error("need exactly one device named " + $name + " (" + (map(.status + " " + .id) | join(", ")) + ")")
end
')
edgible app create existing \
--non-interactive \
--name mc-java \
--port "$MC_JAVA_PORT" \
--protocol tcp \
--auth-modes none \
--device-id "$device_id"
edgible app create existing \
--non-interactive \
--name mc-bedrock \
--port "$MC_BEDROCK_PORT" \
--protocol udp \
--auth-modes none \
--device-id "$device_id"
edgible app list
Verify
mc-java is a TCP app on port 25565, with no auth mode.
From any machine with Docker, mc-monitor speaks Java's status protocol:
docker run --rm itzg/mc-monitor status --host <mc-java hostname> --port 25565
That prints version=Paper and the MOTD. In Minecraft Java Edition, add the server <mc-java hostname>:25565.
mc-bedrock is a UDP app on port 29132, with no auth mode.
docker run --rm itzg/mc-monitor status-bedrock --host <mc-bedrock hostname> --port 29132
That prints a version. In Bedrock Edition, add a server with the mc-bedrock hostname and port 29132.
The rest of the setup is in the Docker Minecraft Server docs.
Tear down
Four steps, in this order. Step 1 runs wherever edgible is logged in. Steps 2 to 4 run on the machine that runs the containers. Steps 2 and 3 read card.env, so keep it until step 4.
1. Unpublish
edgible app delete mc-java --yes
edgible app delete mc-bedrock --yes
2. Stop
docker compose --env-file minecraft/card.env -f minecraft/docker-compose.yml down
3. Delete the data
Skip this step to keep the data. The loop copies each volume to a .tgz file in this directory first.
for volume in $(docker volume ls -q --filter label=com.docker.compose.project=minecraft); do
docker run --rm -v "$volume:/data:ro" -v "$PWD:/backup" alpine tar -czf "/backup/$volume.tgz" -C /data .
done
docker compose --env-file minecraft/card.env -f minecraft/docker-compose.yml down --volumes
4. Remove the card
rm -rf minecraft