Self-hosting Hytale with the community indifferentbroccoli/hytale-server-docker
image is straightforward on paper. Pull, mount a data folder, run. Nobody warns you what happens when that
mount points at the wrong path. The server runs fine, players build for weeks, and every byte of it lands
somewhere you will never think to look during a routine migration.
The indifferentbroccoli/hytale-server-docker image is the go-to for self-hosting Hytale on
Linux. A minimal compose file looks like this:
services:
hytale:
image: indifferentbroccoli/hytale-server-docker:latest
network_mode: host
environment:
DOWNLOAD_ON_START: "false" # see the version-pinning section below
MAX_MEMORY: 3G
volumes:
- /opt/hytale/data:/home/hytale/server-files
That last line, the container-side mount path, is the whole story of this guide.
Migrating an existing Hytale server to new hardware turned up two candidate data directories. One was a single bind-mount, last written months earlier, holding a small, essentially untouched world. Empty mods list, no build history. The other was the debris of one troubleshooting session. Five separate containers, all created and destroyed within about half an hour, all pointed at a bind-mount that was now completely empty.
The obvious read was that the small, older save was the real one, since it was the only bind-mount with data in it. I migrated it over and brought the server up. The world was empty. No houses, nothing built, on a server that had clearly been played for a while before the migration.
Reading the image's own launch scripts, rather than trusting whatever bind-mount path an existing deployment
happened to use, gave the answer. The server writes its data to /home/hytale/server-files inside
the container. All five of the troubleshooting session's containers had their bind mount pointed at
/server-files, missing the /home/hytale prefix entirely.
With DOWNLOAD_ON_START=true, each of those containers downloaded and ran a completely real
server. Players connected, built, played for weeks. The bind mount was watching the wrong path, so none of
that touched the host filesystem. Every byte went into the container's own internal writable layer, invisible
to anyone looking at the host directory the compose file claimed to persist data to. The small, real-looking
save from the other bind-mount was a near-total decoy. A separate, essentially never-played instance that
happened to be the only one with anything visible on disk.
The real world was still there, untouched inside one of the stopped troubleshooting containers. Stopped, not
removed. docker cp pulls individual files out of a stopped container. For a whole directory tree
it is easier to export the full container filesystem as a tarball and filter it to the path that
matters:
docker export <stopped-container-name> | \
tar -x -C <staging-dir> --strip-components=3 'home/hytale/server-files' \
--exclude='home/hytale/server-files/.cache' \
--exclude='home/hytale/server-files/downloader'
From there only the save data went onto the new host. universe/, mods/,
whitelist.json, bans.json, permissions.json, config.json.
The extracted server jar and assets stayed behind in favour of a current build. An old stopped container is
very likely running a stale server version by the time you recover from it.
Once the real save was deployed with its full mod list, the server crash-looped on boot. Blaming the mods
immediately was tempting. A stale mod incompatible with whatever server version shipped since the save was
last played is a reasonable first guess. It turned out to be exactly that, for one of the seven
mods. A hard ClassNotFoundException on an event class that no longer exists in the current
server API. Removing that one mod stopped the crash-loop outright. A second, cosmetic decode warning on a few
base-game armor assets remained from the rest of the mod set. It was not what crashed the server. Isolating
the two symptoms, one fatal and one not, mattered more than reacting to the first error message in the
log.
One detail in the boot log confirmed the recovered save was genuinely the real one, not a fresh world that happened to load without error. In-game elapsed time jumped from a fresh world's baseline straight to several in-game years, matching real play history rather than a clean slate.
host:port string into a
single address field and you get a generic formatting error that reads like a server-side problem. If the
error persists after you split the fields correctly, delete the server entry and re-add it fresh. A stale or
partially pasted previous attempt is far more likely than anything on the server.
When migrating any containerized game server, do not trust "the bind-mount directory has files in it" as proof you found the real data. Check the specific markers of real play. Real chunk counts, real elapsed time, real player history. Not the presence of some data. And when a container's launcher has an internal path convention, read the image's own source before you trust an existing deployment's configuration. A silently wrong path runs a perfectly healthy server for weeks while writing every bit of progress somewhere you will never think to back up.