setupmc.com

Migrate an Existing Minecraft Server or World to Docker

Move an existing Minecraft server into itzg/minecraft-server without mixing migration and upgrades. This runbook covers world-only imports, full data moves, permissions, and rollback.

Docker Operations
setupmc.com Team

Need a cleaner Java baseline?

Generate a Java Compose setup before you keep patching by hand

If you are still refining the Java server baseline, use the Java configurator and then return to the guides for the next issue.

Open Java configurator

Separate migration from change

The first Docker start should reproduce the old server as closely as possible:

  • same Minecraft version
  • same server type or mod loader
  • same world name
  • same plugins or mods and configuration

Only the runtime location should change. Upgrade later, after Docker has passed a real backup and gameplay test.

Prerequisites

  • console access to stop the source server cleanly
  • a complete source backup stored outside the migration directory
  • the exact Minecraft, server, loader, plugin, and mod versions
  • enough space to keep the original server untouched
  • a Docker host directory owned by the container UID, normally 1000:1000

If the ecosystem is uncertain, identify it with the server software comparison before copying data.

Step 1: freeze and inventory the source

Record level-name from server.properties, software/version output, Java version, plugin/mod list, and all external ports. Then stop the source server cleanly and confirm its process is gone.

Create a complete archive before changing any file:

tar -czf "minecraft-source-$(date +%Y%m%d-%H%M%S).tgz" /path/to/old-server

Store that archive outside /path/to/old-server so it does not include itself.

Option A: import one world

Use the image's WORLD mechanism when you want the world but intentionally want fresh server configuration. Place the stopped source world under ./import/world:

services:
  mc:
    image: itzg/minecraft-server:java25
    restart: unless-stopped
    ports:
      - "127.0.0.1:25565:25565"
    environment:
      EULA: "TRUE"
      TYPE: PAPER
      VERSION: "26.1.2"
      LEVEL: world
      WORLD: /import/world
    volumes:
      - ./data:/data
      - ./import:/import:ro

The image searches the source for level.dat and copies it into the directory named by LEVEL. The import normally runs only when that target world does not exist. Do not add FORCE_WORLD_COPY: "TRUE"; that would overwrite the target on every start.

If the archive contains more than one level.dat, use WORLD_INDEX only after checking which result is the intended world.

Option B: migrate the complete server data

Use this when plugins/mods and their configuration must move too. Start with an empty ./data and copy the stopped server contents:

mkdir -p data
cp -a /path/to/old-server/. ./data/
sudo chown -R 1000:1000 ./data

Then use the same /data mount, TYPE, VERSION, loader, and Java release as the source. The image manages its server executable; old start scripts and JARs can remain in the archived source rather than becoming part of the new operating procedure.

If ownership is not 1000:1000 in your Compose configuration, apply the configured UID and GID instead. Use the permission-denied guide when /data is not writable.

Paper migration boundary

Copying files is not a substitute for a supported server-type migration. Current Paper documentation says:

  • Vanilla can migrate to Paper using Paper's documented process
  • Fabric/Forge worlds with custom mod data cannot simply become Paper worlds
  • direct Spigot/CraftBukkit to Paper migration is fundamentally impossible as of 26.1; the documented route goes through Vanilla first

Complete that conversion on a copy before introducing Docker, or reproduce the original server type in Docker first.

Start privately and validate

Binding the port to 127.0.0.1 prevents remote players from joining during the first check.

docker compose config
docker compose up -d
docker compose logs -f mc
docker compose exec mc rcon-cli version
docker compose exec mc rcon-cli save-all flush

Verify the expected seed and landmarks, Nether and End, player inventories and positions, permissions, plugins/mods, and a stop/start cycle. Then create and restore-test a Docker backup before changing the port binding for players.

Roll back

Stop the Docker stack and restart the untouched source installation. Never run both instances against the same world directory, and never expose both on the same port.

Keep the source and its archive until the Docker server has operated normally through backups, restarts, and representative gameplay.

Troubleshooting

SymptomLikely causeSafe response
A new empty world appearsWrong LEVEL, WORLD, or bind mountStop, inspect level.dat paths, and retry with empty target data
Nether or End looks resetUnsupported server-type conversion or wrong world layoutStop immediately and follow the source software's migration docs
/data is read-onlyHost ownership does not match UID/GIDCorrect ownership; do not use chmod -R 777
Plugins or mods failType, loader, version, Java, or dependencies differReproduce the source stack before upgrading anything
Remote players can join during testingPort was exposed on all interfacesBind to 127.0.0.1 until validation is complete

Next steps

Frequently asked questions

Short answers to the questions that usually come up while working through this topic.