A public Enshrouded dedicated server in Docker, tunneled to hide the home IP, with a split-horizon DNS setup so LAN players connect directly instead of round-tripping through the internet. Works great — until a container that's been perfectly healthy the entire time turns out to have never actually had its port open to the host, and only one of the two ways in ever noticed.
Enshrouded's dedicated server runs happily under Docker on a plain Linux container — no Windows
requirement, no special hardware. The community sknnr/enshrouded-dedicated-server image
handles the Wine layer needed to run enshrouded_server.exe on Linux, listening on a single
UDP port (15637 by default).
To expose it publicly without leaking a home IP, an frpc sidecar container runs alongside
it on its own Docker bridge network, tunneling out to a relay server. Because both containers share that
private bridge, frpc reaches the game process over Docker's own internal DNS
(enshrouded:15637) rather than dialing 127.0.0.1 — worth knowing, because it's
exactly what let the bug below hide for as long as it did.
Public traffic is fine routing through a tunnel, but there's no reason a player sitting on the same LAN as the server should take that detour. A local DNS override on the router resolves the server's public hostname straight to its real LAN IP for anyone inside the network, while everyone outside still gets routed through the tunnel normally. Same hostname, two different paths depending on where the client actually is.
This is a real, worthwhile optimization — but it comes with a sharp edge covered below: it means the LAN path and the tunnel path are genuinely different network routes, and one working is no proof the other does too.
docker inspect on the game container showed a bare {} for its published ports —
the compose file put it on a private bridge network with the frpc sidecar but never actually
published its port to the host. A host-level port scan confirmed nothing was listening there at all.
The catch: frpc could still reach the game container fine, because they share the same
private Docker network and talk over Docker's internal DNS — so the public tunnel path kept working the
entire time. Only the LAN-direct path, which needs the port actually published to the host's own network
stack, was ever broken. A player connecting from outside the LAN would never have noticed a thing.
This is also why it can look like it broke suddenly with nothing having changed: a client with a cached DNS answer pointing at the public tunnel address keeps working fine until that cache expires, at which point a fresh lookup resolves to the LAN IP via the split-horizon override and hits a port that was never actually open — from the player's side, that looks exactly like a random outage.
Publish the port explicitly in the compose file — being reachable over the tunnel's own internal Docker network is a completely different claim than being reachable on the host's network stack:
services:
enshrouded:
# ...
ports:
- "15637:15637/udp"
networks:
- gamenet
Recreate the container (docker compose up -d) and confirm a real listener exists at the
host level, not just inside the container's own namespace — the two are not the same check:
# from inside the LXC/host itself — checks the host's own network stack
ss -ulnp | grep 15637
# from inside the container — checks whether the process is listening at all,
# useful on minimal images that don't even have ss/netstat installed
docker exec enshrouded cat /proc/net/udp
Enshrouded's server browser lists a discoverable dedicated server automatically, at the top — no manual "add server" step needed. Typing the server's name into the browser's own search box, though, reliably returns zero results. That's expected behavior, not a broken tunnel or DNS entry: the server's already visible without searching for it, the search field just doesn't match against it. Connecting straight from the list works fine.
Enshrouded has no official mod support, and since this server runs the game under Wine inside Docker
rather than natively, most mod loaders' requirement to set a WINEDLLOVERRIDES environment
variable on the actual Wine process invocation is unconfirmed against this specific image's entrypoint —
other people running the same base image have hit the identical wall with no documented fix. Worth
verifying against a disposable test instance with a fresh backup on hand before ever trying it against a
server real players are connected to.