Docker & DevOps

Host a Minecraft Server with Docker Compose on a Linux VPS

Run a Java Edition Minecraft server in Docker Compose on a Linux VPS, with a persistent world volume, memory limits, a whitelist and a plain backup routine.

BeginnerLinux server5 min read

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.

Bash
mkdir -p ~/minecraft && cd ~/minecraft

Put the RCON password in an .env file next to the compose file so it never lands in Git.

INI
RCON_PASSWORD=replace-with-a-long-random-string

Now 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.

YAML
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: 60s

Two 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.

Bash
sudo ufw allow 25565/tcp
docker compose up -d

Watch the first boot. The server downloads the jar, generates the world and then prints Done. First start can take a few minutes.

Bash
docker compose logs -f minecraft

Look 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:

YAML
      INIT_MEMORY: "2G"
      MAX_MEMORY: "3G"

Optionally cap the container too, so a runaway world can never starve the rest of the VPS:

YAML
    mem_limit: 3500m

Apply 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:

Bash
docker exec -i minecraft rcon-cli

Then inside the prompt:

whitelist add Steve
whitelist reload
op deploy
list

Type 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:

Bash
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-on

Run 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

  1. docker compose ps shows the minecraft container as running (and healthy, since the image ships its own healthcheck).
  2. docker compose logs --tail=50 minecraft ends with Done and no repeating Java exceptions.
  3. From the Minecraft client: Multiplayer → Add Server, enter 203.0.113.10 (your VPS IP) as the address and join.
  4. 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 ps and sudo 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 MEMORY or MAX_MEMORY, or move to a larger plan; adding swap helps but slows the game.
  • Log says "You need to agree to the EULA". The EULA variable is missing or not exactly "TRUE" in compose.yaml. Fix it and run docker compose up -d.
  • Whitelist edits have no effect. Changing WHITELIST only applies on restart. Use docker exec -i minecraft rcon-cli and run whitelist add <name> followed by whitelist 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 with sudo chown -R 1000:1000 ~/minecraft/data.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.