d2-dedicated-server: A Cloud-Native Diablo II Realm Without PvPGN
By Indie Kings | November 4, 2026
Updated November 4, 2026: d2-dedicated-server is a self-hosted, cloud-native Diablo II dedicated game server and Battle.net realm in Zig, presented as a modern replacement for PvPGN. It runs seven game engines from 1.06b through 1.13c on the game's own DLLs, 1.14d through Wine, and a Wine-free native build ported from the macOS release. The repository holds 34 stars and 4 forks under MIT, and version 0.5.0 shipped on October 18, 2026.
Image: GitHub social preview card for the d2-dedicated-server repository. Credit: jaenster/GitHub.
Per the official repository README, d2-dedicated-server is a headless, cloud-native Diablo II dedicated game server and realm. The project describes itself as a modern replacement for PvPGN, the long-serving open source Battle.net emulator that has hosted private Diablo II realms for years. It is written in Zig, and its components ship as separate containers rather than as one monolith.
The README is direct about the design decision that shapes everything else. The project does not aim to be compatible with Windows or to run as a single process. It commits to Linux containers so it can lean on POSIX behaviour, and the goal is explicit: to make the realm and ingress services as lightweight and stateless as possible. That is an unusual choice for a game server project, where the conventional instinct is to keep everything in one binary that users can run anywhere.
Three containers, one realm
The architecture splits into three services with clear boundaries. The realmd container is the realm itself: one process doing every job that PvPGN split across daemons, meaning login and chat, character select, and game create and join. To the client it behaves as Battle.net does, on port 6112. The d2gs container is the headless game server, in several variants. The d2ingress container handles game traffic, presenting one public address in front of the whole fleet and routing each connection by the game's own protocol.
Two external services complete the picture. Redis is described as the source of truth during runtime, the link between the realm, the game servers and the ingress. PostgreSQL is the long-term store for account data, character data and realm information. The README's summary of where state lives is the clearest statement of the design: the only truly stateful application is PostgreSQL, because that holds the actual long-term storage, while Redis keeps in memory what is happening live on the realms.
| Component | Role | State |
|---|---|---|
| realmd | Login and chat, character select, game create and join, on port 6112 | Stateless, can run as multiple pods |
| d2gs | Headless game server, multiple engine variants | Takes work from its queue and reports results |
| d2ingress | One public address for the whole fleet, routing per connection | Routes on the game's own protocol |
| Redis | Live characters, seats, tokens, routes, fleet state, game server queues | In-memory runtime source of truth |
| PostgreSQL | Accounts, characters and long-term realm data | The only truly stateful component |
The README's list of what cloud-native means here is worth reading as a set rather than as individual claims. Every pod can be scaled, with no leader selection, because Redis and PostgreSQL handle coordination. Redis solves the problem of one character being in multiple games, because each game server owns a lock on the characters in its game. Realm-wide chat goes through Redis, with each realm pod a client of it, which removes the leader selection problem for realmd entirely. Nothing except the database touches disk, logs are structured JSON for modern log aggregators, and metrics are exposed for Prometheus.
Seven engines and a Wine-free build
The engine matrix is the most practically useful part of the project for anyone who has run a private realm. Per the README, the server supports 1.06b, 1.07, 1.08, 1.09b, 1.09d, 1.10f and 1.13c using the game's own DLLs, which are replaceable with your own versions. The 1.06b image is specifically for the classic game without the expansion. The 1.14d image runs the headless server from a 1.14d install through Wine.
The variant that stands out is the native build. The README describes 1.14d-native as a special port of the macOS 1.14d version to Linux, to run natively without Wine. That is a meaningful step, because it removes the compatibility layer from the hot path of a game server that has to stay up. It is also the kind of work that only happens when somebody has committed to running this in production rather than treating it as a weekend experiment.
The container naming makes the trade-off visible at the point of use. The table lists each engine variant with its Wine requirement and whether it needs a volume, and the pattern is consistent: the older engines and the Wine-based 1.14d all use Wine with no volume needed, while the native build uses no Wine at all. A user choosing an image can see exactly what they are taking on without reading further documentation.
What makes it cloud-native rather than containerised
Containerising a server is not the same as designing one for a cluster, and the README makes the distinction concrete. The ingress is the clearest example. Because d2ingress forwards a connection to the right pod once the client tells it which game to connect to, game servers can listen on whatever port they like and only on internal addresses, so a deployment does not need a different public IP per game server. The README notes that you could even host multiple different versions of the game on the same host.
The locking model is the second example. A character being in one game at a time is a correctness requirement in Diablo II, and in a single-process server it is enforced by there being one process. In a fleet it has to be a distributed lock, which is why Redis owns it. The README calls out that no leader selection is needed for realm services because chat flows through Redis and every realm pod is simply a client of it. That is a design that scales horizontally without adding coordination code.
Deployment is aimed at the Kubernetes toolchain rather than at a single Docker command. The README's aim is to host the realm and game server through Helm charts, Argo CD and dashboards in Grafana, and it states what the project is not aiming to do: be compatible with Windows or run as separate containers on a desktop. For anyone who wants a simple self-hosted realm, that is a real constraint, and the README does not hide it.
Why the client only ever uses two ports
The traffic model is the most elegant thing in the design, and the README explains it with a diagram. The client only ever uses two ports: 6112 for login and realm, and 4000 for game traffic. There is no third. The realm and the game servers never speak to each other directly; they meet in Redis. Game traffic always crosses an ingress, because the token the realm hands the client is realm-global and only an ingress can translate it into the identifier the engine knows.
The single-host case is handled without a second binary. On one machine the realm can act as its own ingress. In the cloud it advertises the ingress service instead. The README points out that the same binaries are used either way, so a small deployment and a large one run the same software and differ only in where the routing happens. That is a good property for a project whose users will start small and may grow.
The token exchange matters for security as well as for routing. Because the client never receives a per-server address, a player cannot be handed the internal identity of a game server that the operator would prefer to keep on a private network. The ingress becoming the only public surface for game traffic is a consequence of the token design rather than a separate hardening step added later.
Where the project stands
The repository was created on June 5, 2026, and was last pushed on October 29, 2026. It holds 34 stars, 4 forks and 5 open issues under MIT, and is written in Zig. Five releases are published: version 0.5.0 on October 18, 2026, 0.4.0 on October 5, 0.3.1 on September 9, 0.3.0 on August 22 and 0.1.0 on August 10. Release assets carry no downloads, which is expected for a project consumed as container images rather than as release archives.
The Zig choice is consistent with the author's other work, where the same language carries the clientless protocol client and the legacy downloader. For a project that needs a small static binary that speaks a binary protocol precisely, it is a reasonable fit, and it keeps the container images small. The README also links a dedicated deployment document for the cross-component setup, which suggests a project that has been deployed somewhere real rather than only to a local Docker socket.
The thing to weigh before adopting this is operational surface. A user who wants a single-binary realm server for a handful of friends is not the target audience. A user who wants a realm that can scale, survive a pod restart without losing live state, and be managed with the same tooling as the rest of their infrastructure is. The README is written for the second user and says so.
Our take: the interesting claim here is not that somebody wrote a Diablo II realm server, because several exist. It is that somebody designed one around statelessness, distributed locks and a single public port, and then built seven engine variants on top of the game's own DLLs plus a Wine-free native path. The practical limitation is that this is Kubernetes-shaped software, and a hobbyist who wants a simple realm should read the deployment requirements first.
FAQ
What is d2-dedicated-server?
Per the official repository README, it is a self-hosted, cloud-native Diablo II dedicated game server and Battle.net realm written in Zig, described as a modern replacement for PvPGN.
Do I need to run Kubernetes?
The project is aimed at the Kubernetes toolchain, with Helm charts, Argo CD and Grafana dashboards named as the intent. The README states it is not aiming to run as separate containers on Windows or as a desktop setup, though the components are containers first.
Which game versions are supported?
Per the README, 1.06b, 1.07, 1.08, 1.09b, 1.09d, 1.10f and 1.13c use the game's own DLLs, 1.14d runs through Wine, and a native build of 1.14d runs without Wine.
Why does the client only use two ports?
Per the README, port 6112 carries login and realm traffic and port 4000 carries game traffic, and there is no third. Game traffic crosses the ingress because the token the realm issues is realm-global and only the ingress can translate it for the engine.
Where is state kept?
Per the README, PostgreSQL is the only truly stateful component and holds long-term storage such as accounts and characters, while Redis keeps in memory what is happening live, including characters, seats, tokens, routes, fleet state and game queues.
Can one host run several game versions?
Yes. Per the README, because the ingress routes per connection, game servers can listen only on internal addresses, so you do not need different public addresses per server and could host multiple game versions on the same host.
Bottom Line
d2-dedicated-server is the most deliberate piece of Diablo II server infrastructure we have seen: a stateless realm, a fleet of game servers, one ingress, Redis for live state, PostgreSQL for the record, and a design that reduces the client's entire surface to two ports. Seven engine variants run on the game's own DLLs, and the 1.14d native build removes Wine from the path entirely.
The constraint is that it is built for Linux containers and the Kubernetes toolchain, and the README says it is not aiming to be compatible with Windows or to run as a desktop setup. Version 0.5.0 shipped on October 18, 2026, and the project is small but consistent in direction. If you run infrastructure for a private realm, this is the modern option to evaluate.
Related: wowemulation-dev: Three Open Tools Split the WoW Emulation Stack, D2Decomp: Diablo II Rebuilt as a Native Engine
Sources: per the official d2-dedicated-server repository README at github.com/jaenster/d2-dedicated-server, fetched in full on November 4, 2026, including the component table, engine matrix, architecture diagram and cloud-native design notes; repository metadata (34 stars, 4 forks, 5 open issues, MIT, created June 5, 2026, language Zig, last pushed October 29, 2026) re-verified via the GitHub API on November 4, 2026; release list for v0.1.0 through v0.5.0, the latest dated October 18, 2026, from the same repository.