Deployment of a vanilla Minecraft Java server through Docker Compose, with a playit.gg tunnel for public access, on a remote host managed through a Docker context.
For anyone who wants to host a small public vanilla Minecraft server without configuring their router: Docker handles the server, playit.gg handles the public access. Tested on a Raspberry Pi 5 (ARM64), but nothing here is Pi-specific — any Docker host works.
- Stack & skills
- Architecture
- Requirements
- Installation
- Deployment
- Post-launch checks
- Player access
- Whitelist
- Useful commands
- Changing the seed
- World backup
- Security
- Contributing
- License
This project covers, end to end:
- Containerization: Docker Compose,
network_mode: service:handling, memory limits (mem_limit), persistent volumes - CI/CD: GitHub Actions (
docker compose configvalidation, YAML/Markdown linting, secret scanning with gitleaks) - Operational security: secrets kept outside the repository (
.env),.gitignore, documented whitelist/online-mode choices - System troubleshooting: analysis of a JVM crash caused by memory contention (
free -h,docker stats), broken DNS resolution under a shared network namespace — seeTroubleshooting.en.md - Documentation: versioned changelog (Keep a Changelog), deployment and rollback procedure
flowchart LR
subgraph Host["Remote host (Docker context)"]
MC["mc-vanilla<br/>itzg/minecraft-server<br/>:25565"]
PL["playit-mc<br/>playit.gg agent<br/>network_mode: service:mc-vanilla"]
VOL[("Persistent volume<br/>/data (world)")]
PL -. "shares the network stack of" .-> MC
MC --- VOL
end
LAN["Player (private network/VPN/LAN)"] -- "internal IP :25565" --> MC
WAN["Player (Internet)"] -- "public address" --> TUNNEL["playit.gg tunnel"]
TUNNEL --> PL
The playit container has no network endpoint of its own (network_mode: service:mc-vanilla): it fully shares the network stack of mc-vanilla, which requires a static DNS (resolv-playit.conf) — see Troubleshooting.en.md.
- Docker + Docker Compose installed on the target host
- A playit.gg account (verified email) with a Docker agent created and a Minecraft Java tunnel configured
- A persistent storage folder for the world, outside the system partition if space is limited there
- On Raspberry Pi OS, and on any host where cgroup memory accounting is off at the kernel level, Docker silently discards
mem_limit. Addcgroup_memory=1 cgroup_enable=memoryto/boot/firmware/cmdline.txt(/boot/cmdline.txton older releases) and reboot, otherwise the memory limit in the compose file has no effect — this is what caused the crash described inTroubleshooting.en.md.
-
Copy
.env.exampleto.envand fill in the real values:cp .env.example .env
Edit
.env:PLAYIT_SECRET_KEY: the secret generated by the playit.gg Docker wizard ("Agents" section of the dashboard)MC_SEED: the Minecraft seed you want
-
Create the world data folder (adjust to your own mount point):
mkdir -p /path/to/storage/minecraft/vanilla
Then update the path in
minecraft-vanilla.yml,volumessection of themc-vanillaservice. -
Copy
resolv-playit.conf.exampletoresolv-playit.conf, in the same folder as the compose file:cp resolv-playit.conf.example resolv-playit.conf
This file is required because the
playitcontainer shares the network namespace ofmc-vanilla(network_mode: service:mc-vanilla) and cannot use Docker's internal resolver in that mode — so we mount a static/etc/resolv.confpointing to public DNS servers.Create it before the first
docker compose up: Docker does not fail on a missing bind mount source, it creates a directory with that name instead, and theplayitcontainer then starts without a usable resolver.
docker compose -f minecraft-vanilla.yml up -d# Follow the Minecraft server startup until "Done" (world generation)
docker logs -f mc-vanilla
# Follow the playit agent connection
docker logs -f playit-mc
# Confirm the static DNS is correctly applied to the playit container
docker exec playit-mc cat /etc/resolv.conf
# Check the actual resource usage of both containers
docker stats mc-vanilla playit-mc --no-stream- On the same private network as the host (VPN, LAN, Tailscale, etc.): direct connection to the host's internal IP, port
25565. - Outside the private network: connection through the public address generated by the playit.gg tunnel (visible in the dashboard, "Tunnels" section → your Minecraft Java tunnel).
The whitelist is disabled by default in this config — any player with a legitimate Mojang/Microsoft account (online-mode protection, enabled by default) can join if they know the address. To enable it:
- Add
ENFORCE_WHITELIST: "TRUE"to theenvironmentofmc-vanilla - Manage the allowed players through the console (see below), with
whitelist add <username>
# Stop the server (without removing containers/volumes)
docker compose -f minecraft-vanilla.yml stop
# Restart
docker compose -f minecraft-vanilla.yml start
# Stop everything and remove the containers (the world stays on the volume)
docker compose -f minecraft-vanilla.yml down
# Attach to the server console (whitelist, op, etc.)
docker attach mc-vanilla
# To detach without stopping the server: Ctrl+P then Ctrl+QIn the Minecraft console:
op <username> # grant admin rights
whitelist add <username>
whitelist list
whitelist remove <username>
Edit MC_SEED in .env, then delete the existing world before restarting (otherwise the new seed is ignored):
rm -rf /path/to/storage/minecraft/vanilla/world
docker compose -f minecraft-vanilla.yml up -dThe backup.sh script archives the world and applies a rotation (7 backups kept by default):
chmod +x backup.sh
./backup.sh /path/to/storage/minecraft ./backups 7To automate it, add a cron entry on the host (example: daily backup at 4am):
0 4 * * * /path/to/backup.sh /path/to/storage/minecraft /path/to/backups 7.envcontains secrets and is never committed (see.gitignore) — only.env.exampleshould be versioned.- Since the server listens without a whitelist, consider enabling it if the public tunnel stays open for a long time without active supervision.
See CONTRIBUTING.md for the development environment, how to reproduce the CI checks locally, and the PR format.
This project is licensed under the MIT license.