A Minecraft server with Docker Compose takes about ten minutes to set up: one compose file, one volume for the world and one environment variable to accept the EULA. This guide runs a Java Edition server on a Linux VPS, sets sane memory limits, locks it down with a whitelist and shows a backup you can put in cron.
The same pattern works for Paper, Fabric, Forge and vanilla. You only change the TYPE variable.
Before you start
You need:
- A Linux VPS with at least 2 GB of RAM for a small vanilla server — 4 GB or more is comfortable with plugins. A Linux VPS gives you root and full control over ports.
- Docker and the Compose plugin installed. If not, follow Install Docker and Docker Compose on a Linux VPS.
- SSH access as
deploy(or root). - The IP address of your server, shown in your VPS provider's control panel.
Tip: Minecraft Java Edition uses TCP port 25565 only. Bedrock uses UDP 19132, so a Bedrock client cannot join a Java server without a translation layer.
Create the project and compose file
Work in a directory you can back up easily.
mkdir -p ~/minecraft && cd ~/minecraftPut the RCON password in an .env file next to the compose file so it never lands in Git.
RCON_PASSWORD=replace-with-a-long-random-stringNow create compose.yaml. The image is itzg/minecraft-server; check Docker Hub for the current Java tag that matches your Minecraft version, since that changes over time.
services:
minecraft:
image: itzg/minecraft-server:java21
container_name: minecraft
tty: true
stdin_open: true
ports:
- "25565:25565"
- "127.0.0.1:25575:25575"
environment:
EULA: "TRUE"
TYPE: "PAPER"
VERSION: "LATEST"
MEMORY: "4G"
DIFFICULTY: "normal"
MAX_PLAYERS: "10"
ENABLE_RCON: "true"
RCON_PASSWORD: "${RCON_PASSWORD}"
WHITELIST: "deploy"
OPS: "deploy"
ENABLE_WHITELIST: "true"
volumes:
- ./data:/data
restart: unless-stopped
stop_grace_period: 60sTwo things matter here. ./data:/data keeps the world, configs and plugins outside the container, so a recreated container never wipes your world. 127.0.0.1:25575 binds RCON to localhost only — never expose RCON to the internet.
Warning: Set EULA: "TRUE" only if you accept the Minecraft End User Licence Agreement. The server refuses to start otherwise.
Open the port and start the server
Allow game traffic through the firewall, then bring the stack up.
sudo ufw allow 25565/tcp
docker compose up -dWatch the first boot. The server downloads the jar, generates the world and then prints Done. First start can take a few minutes.
docker compose logs -f minecraftLook for a line like Done (12.345s)! For help, type "help".
Set memory limits correctly
MEMORY is the value passed to the Java heap, not a Docker limit. Leave headroom for the JVM itself, the OS and Docker — a 4 GB VPS should run about 2.5–3 GB of heap, not 4 GB.
For finer control, split the two:
INIT_MEMORY: "2G"
MAX_MEMORY: "3G"Optionally cap the container too, so a runaway world can never starve the rest of the VPS:
mem_limit: 3500mApply changes with docker compose up -d — Compose recreates the container and reuses the existing ./data volume.
Manage the whitelist and operators
The WHITELIST and OPS variables are applied on startup. To change them live, use RCON:
docker exec -i minecraft rcon-cliThen inside the prompt:
whitelist add Steve
whitelist reload
op deploy
listType exit to leave. Keeping the whitelist on is the single most effective protection for a public IP address, because open servers get scanned and griefed quickly.
Back up the world
Warning: Never copy the world folder while the server is writing to it. You can get a truncated or inconsistent region file that fails to load.
The safe sequence pauses saving, flushes to disk, copies, then resumes:
docker exec minecraft rcon-cli save-off
docker exec minecraft rcon-cli save-all flush
tar -czf ~/minecraft-world-$(date +%F).tar.gz -C ~/minecraft data
docker exec minecraft rcon-cli save-onRun it from cron nightly and copy the archive off the VPS. For a proper encrypted, versioned routine, see Back up Docker volumes with restic and restore them.
Check it works
docker compose psshows theminecraftcontainer as running (and healthy, since the image ships its own healthcheck).docker compose logs --tail=50 minecraftends withDoneand no repeating Java exceptions.- From the Minecraft client: Multiplayer → Add Server, enter
203.0.113.10(your VPS IP) as the address and join. - A player who is not on the whitelist sees
You are not whitelisted on this server— that confirms the whitelist is active.
Troubleshooting
- Client says "Connection refused" or "Can't connect to server". Either the server is still generating the world, or port 25565 is blocked. Check
docker compose psandsudo ufw status, and confirm your provider's firewall allows TCP 25565. - Container exits immediately or with code 137. The JVM is out of memory. Lower
MEMORYorMAX_MEMORY, or move to a larger plan; adding swap helps but slows the game. - Log says "You need to agree to the EULA". The
EULAvariable is missing or not exactly"TRUE"incompose.yaml. Fix it and rundocker compose up -d. - Whitelist edits have no effect. Changing
WHITELISTonly applies on restart. Usedocker exec -i minecraft rcon-cliand runwhitelist add <name>followed bywhitelist reload. - World is empty after a redeploy. The volume path changed or you removed
./data. If the server runs as UID 1000 and the folder is owned by root, it may also fail to write — fix ownership withsudo chown -R 1000:1000 ~/minecraft/data.
Next steps
- Put the server data on a safer footing with Docker persistent storage for non-root users on a Linux VPS.
- Automate nightly archives using Back up Docker volumes with restic and restore them.
- Watch memory and CPU over time with Monitor Docker containers with Prometheus, cAdvisor and Grafana.
