Bedrock Edition is the easiest Minecraft variant to self-host — no mod loader, no Java heap tuning, a single well-maintained Docker image. The interesting part isn't standing it up from scratch, it's moving a world that's already been played on for real without silently corrupting it in the process. (Running Java too, for mods? See the separate guides on legacy Forge and modern Fabric — Bedrock can't run either.)
itzg/minecraft-bedrock-server
is the de facto standard — actively maintained, tracks Mojang's Bedrock server releases quickly, and exposes
every server-property as an environment variable so a whole world's config lives in one
docker-compose.yml instead of hand-edited property files:
services:
minecraft-bedrock:
image: itzg/minecraft-bedrock-server
restart: unless-stopped
environment:
EULA: "TRUE"
SERVER_NAME: "Family Server"
GAMEMODE: survival
DIFFICULTY: normal
ports:
- "19132:19132/udp"
volumes:
- /opt/minecraft/data:/data
That's genuinely the whole setup for a fresh world. The gotchas below are specifically about moving an existing world onto a server like this, not about first-time setup.
It's common for a Minecraft world to start life on whatever Docker host was already running — a NAS, a shared homelab VM, whatever was convenient at the time. That's fine for a while, but it means the world isn't isolated from unrelated experiments on the same host, and it's easy for it to end up outside your actual backup coverage if backups are scoped per-VM/per-container rather than blanket-covering the whole box. Worth migrating onto its own small dedicated container specifically so it gets its own backup job entry and can't be affected by anything else running on the source host.
The migration itself is a plain two-hop copy — nothing Minecraft-specific about it:
ssh <old-host> "sudo tar -C /path/to/world -cf - ." | tar -C <local-stage> -xf -
tar -C <local-stage> -cf - . | ssh <new-host> "tar -C /opt/minecraft/data -xf -"
Using sudo tar at the source rather than a network filesystem client (NFS, SMB) sidesteps any
permission-mapping weirdness the file-sharing layer might introduce on top of what's described below.
If the world's previous home was a NAS (Synology, QNAP, etc.), expect a stray thumbnail/indexing folder
(Synology's is @eaDir) to come along in a naive recursive copy. It's harmless — the server
doesn't touch it — but worth deleting after the copy just to keep the data directory clean, since it'll
otherwise sit there confusing anyone who goes looking through the folder later.
behavior_packs/<pack-name>/
folder left as dr-xr-xr-x (read-only, no write bit at all) from however it was originally
extracted. The container image needs to rewrite that directory on every boot as part of its own
internal version-migration check — if it can't, you get an immediate Permission denied and a
crash-loop that has nothing to do with the actual world data being corrupt. The fix is a blanket permission
reset on the whole data directory before first boot on the new host:
chmod -R u+w /opt/minecraft/data
Do this as a standard step on any world you're migrating from another host, not just as a reactive
fix once you hit the crash — it costs nothing and avoids a confusing debugging session that looks like data
corruption but isn't.
Bedrock's protocol runs entirely over UDP (RakNet), which rules out any tunnel solution that only forwards
TCP. A UDP-capable tunnel — either a lightweight P2P tunnel service or your own small relay VPS running
something like frp — forwarded straight to 19132/udp
is all that's needed; no port-forwarding on your home router, no static IP required.
One client-side gotcha worth knowing about ahead of time, specific to Bedrock's own "Add Server" dialog (and
a few similar Bedrock-ecosystem clients, Hytale's included): the address field wants host and port
separately. Pasting a combined host:port string into the single address field
produces a generic "server address is not correctly formatted" error that has nothing to do with whether the
server itself is actually reachable — it'll fail identically whether the server is up or down. If that error
shows up after correctly splitting the fields too, delete the saved server entry and re-add it from scratch
rather than editing it; a stale partially-pasted previous attempt is a more likely cause than an actual
server-side problem.