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
| Symptom | Likely cause | Safe response |
|---|---|---|
| A new empty world appears | Wrong LEVEL, WORLD, or bind mount | Stop, inspect level.dat paths, and retry with empty target data |
| Nether or End looks reset | Unsupported server-type conversion or wrong world layout | Stop immediately and follow the source software's migration docs |
/data is read-only | Host ownership does not match UID/GID | Correct ownership; do not use chmod -R 777 |
| Plugins or mods fail | Type, loader, version, Java, or dependencies differ | Reproduce the source stack before upgrading anything |
| Remote players can join during testing | Port was exposed on all interfaces | Bind to 127.0.0.1 until validation is complete |
Next steps
- Protect the Docker copy with automatic backups.
- After the migration is stable, use the safe update and rollback runbook.
- For a Modrinth pack, follow the dedicated modpack guide.