← Back to Wiki
Game Servers / Hytale

Self-Hosting Hytale: Finding the Real Save Among Broken Containers

Self-hosting Hytale with the community indifferentbroccoli/hytale-server-docker image is straightforward on paper — pull, mount a data folder, run. The part nobody warns you about is 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'll never think to look during a routine migration.

Share on X

The setup

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.

When "which save is the real one" isn't obvious

Migrating an existing Hytale server to new hardware turned up two candidate data directories. One was a single bind-mount, last written months earlier, containing a small, essentially-untouched world (empty mods list, no real build history). The other was the debris of a single 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: the small, older save was the "real" one, since it was the only bind-mount that actually had data in it. Migrated it over, brought the server up — and the world was empty. No houses, nothing built, despite the server having clearly been played on for a while before the migration.

An empty bind-mount directory doesn't mean an empty world. Both candidate save locations were checked for real chunk/player data, and neither had any. The actual gameplay had to be somewhere else entirely — which meant the assumption "the save lives in the folder the compose file points at" was wrong for this deployment, and had apparently been wrong for as long as it existed.

The bind-mount path mismatch that caused it

Reading the image's own launch scripts directly (rather than trusting whatever bind-mount path an existing deployment happened to use) revealed the actual answer: the server writes its data to /home/hytale/server-files inside the container. The troubleshooting session's five containers were all configured with 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. But because the bind mount was watching the wrong path, none of that ever touched the host filesystem — every byte of it was written into the container's own internal writable layer, invisible to anyone looking at the host directory the compose file claimed to be persisting data to. The small, "real-looking" save from the other bind-mount was a near-total decoy — a separate, essentially never-played instance that just happened to be the only one with anything visible on disk.

Recovering data trapped in a container's writable layer

The actual world was still there, sitting untouched inside one of the stopped (not removed) troubleshooting containers. docker cp works for pulling individual files back out of a stopped container, but for recovering a whole directory tree it's easier to export the full container filesystem as a tarball and filter it down 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 actual save data (universe/, mods/, whitelist.json, bans.json, permissions.json, config.json) got copied onto the new host — the extracted server jar and assets were deliberately left behind in favor of a current build, since an old stopped container is very likely running a stale server version by the time you're recovering from it.

The generalizable move: when an image's launcher hardcodes an internal data path, verify what that path actually is by reading the image's own scripts — never assume an existing bind-mount config is correct just because it's already in production. A wrong path doesn't fail loudly; it just quietly writes everything into a disposable layer instead of your persistent volume, and the symptom you eventually see (an "empty" world after a migration) looks nothing like its actual cause.

A crash that looked like a mod problem, wasn't

Once the real save was deployed with its full mod list, the server crash-looped on boot. It was tempting to blame the mods immediately — a stale mod incompatible with whatever server version had shipped since the save was last played is a completely reasonable first guess. It turned out to be exactly that, but only for one of the seven mods: a hard ClassNotFoundException on an event class that no longer exists in the current server API. Removing that single 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, but it wasn't what was actually crashing the server — isolating the two symptoms (one fatal, one not) mattered more than reacting to the first error message seen in the log.

Confirming the recovered save was genuinely the real one, not a fresh world that happened to load without error, came down to one detail in the boot log: in-game elapsed time jumping from a fresh world's baseline straight to several real in-game years, matching actual play history rather than a clean slate.

A client-side gotcha worth knowing about

"Server address is not correctly formatted" often has nothing to do with the server. Hytale clients' "Add Server" screen (and several similar dialogs on Bedrock-style clients generally) want the host and port in separate fields. Pasting a combined host:port string into a single address field throws a generic formatting error that reads like a server-side problem but isn't. If the error persists after splitting the fields correctly, delete the server entry and re-add it fresh — a stale or partially-pasted previous attempt is a far more likely cause than anything on the server.

The lesson that generalizes

When migrating any containerized game server, don't trust "the bind-mount directory has files in it" as proof you've found the real data — check for the specific markers of actual play (real chunk counts, real elapsed time, real player history), not just the presence of some data. And when a container's launcher has an internal path convention, read the image's own source before trusting an existing deployment's configuration — a silently wrong path can run a perfectly healthy server for weeks while writing every bit of progress somewhere you'll never think to back up.