Skip to content

biers04/windrose_docker

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Windrose Dedicated Server on Linux

This project runs the current Windows-only Windrose dedicated-server files inside Docker on Linux by using Wine.

It is designed around the current public setup reality on April 17, 2026:

  • The official Steam discussion and the bundled DedicatedServer.md describe a Windows server executable and JSON config flow.
  • The dedicated server Steam app id is 4129620.
  • Anonymous Linux SteamCMD still does not work for Windrose content download, but authenticated SteamCMD with a real Steam account is now working for install and update checks in this Docker workflow.
  • The container validates the Steam Tools root launcher WindroseServer.exe, but it runs the Unreal shipping binary R5/Binaries/Win64/WindroseServer-Win64-Shipping.exe directly under Wine for better stability in headless Docker.

Source used:

What this gives you

  • A Docker image that can run the Windows dedicated server on Ubuntu.
  • A clean project layout for one or more Windrose instances.
  • Editable ServerDescription.json and WorldDescription.json files outside the runtime tree.
  • Authenticated SteamCMD install and update checks on container boot.

Project layout

config/
  ServerDescription.json
  WorldDescription.json
logs/
runtime/
source/
steamcmd/
.env
compose.yaml
  • source/: copy the Windows dedicated-server files here.
  • runtime/: the container runs from this writable tree and refreshes it from source/ on startup.
  • config/: the JSON files you edit on the server. The container syncs admin-owned settings into the runtime tree on launch.
  • logs/: container and Wine logs.
  • steamcmd/: persisted SteamCMD home directory for authenticated update checks and cached login state.
  • .env: optional Steam credentials and SteamCMD settings for automatic update checks.

Getting the server files

Recommended source:

  1. Use authenticated SteamCMD through this Docker workflow.
  2. Set your Steam credentials in .env.
  3. Start the container and let it install or update app 4129620 into ./source/.

This is the recommended ongoing setup because it also keeps the server files current on later container restarts.

Manual fallback:

  1. On a Windows machine with Steam, open Library.
  2. Change the library filter to Tools.
  3. Install Windrose Dedicated Server.
  4. Copy or zip the installed folder and place its contents into source/.

This still matches the official developer guidance and the bundled DedicatedServer.md, and it remains a good fallback if you do not want to store Steam credentials in .env.

Other notes:

  1. Authenticated SteamCMD with a licensed Steam account: Supported in this repo for install and update checks.

  2. Anonymous SteamCMD: Not reliable at the moment. Metadata works, but content download did not.

Local run

cp compose.example.yaml compose.yaml
cp .env.example .env
docker compose build
docker compose up -d
docker compose logs -f

docker compose build downloads SteamCMD into the image automatically. You do not need to install SteamCMD separately on the host.

If SteamCMD is disabled and the Windows server files are missing, the container exits with a clear message telling you which directory to populate.

Quick start

If you just want the shortest path to a working test server on Linux:

  1. Install the Linux-side requirements with the helper script.
  2. Copy this repo to the Linux host.
  3. Set your Steam credentials in ./.env.
  4. Start the container.
  5. Read the invite code.
  6. Join from the game client with Play -> Connect to Server.

Recommended host bootstrap:

sudo ./scripts/install-host-requirements.sh

Example:

git clone https://github.com/biers04/windrose_docker.git windrose-dedicated
cd windrose-dedicated
sudo ./scripts/install-host-requirements.sh
cp .env.example .env
# Edit .env and set STEAM_UPDATE_ON_BOOT=true plus your Steam credentials
docker compose build
docker compose up -d
docker compose logs -f
./scripts/show-invite-code.sh .

At that point:

  • the server should be running in Docker
  • runtime/ServerDescription.json should exist
  • the invite code printed by show-invite-code.sh is what players use to join

If you later change settings in config/ServerDescription.json or config/WorldDescription.json, restart the container:

docker compose up -d --force-recreate

Changing server settings

Edit these files on the Linux host:

  • config/ServerDescription.json
  • config/WorldDescription.json

For example:

nano config/ServerDescription.json
nano config/WorldDescription.json

After saving your changes, restart the container:

docker compose up -d --force-recreate

Then verify the server and invite code:

docker compose logs -f
./scripts/show-invite-code.sh .

SteamCMD updates on boot

Recommended setup: let the container use authenticated SteamCMD so it can install and update Windrose every time it starts or restarts.

Copy the example environment file and set your Steam credentials:

cp .env.example .env
nano .env

Or use the helper:

./scripts/edit-steam-env.sh .

Example:

STEAM_UPDATE_ON_BOOT=true
STEAM_USERNAME=your-steam-account
STEAM_PASSWORD=your-steam-password
STEAMCMD_APP_ID=4129620
STEAMCMD_PLATFORM=windows
STEAMCMD_VALIDATE=false

How it works:

  • On container start, the image runs an authenticated SteamCMD app_update against app 4129620.
  • The install target is ./source, so Steam-managed server files stay outside the writable runtime tree.
  • The container then rsyncs source/ into runtime/ before launch so updated binaries are picked up on the same boot.
  • SteamCMD state is persisted in ./steamcmd, which lets cached login state survive reboots.

Important:

  • Anonymous SteamCMD was not enough for Windrose during testing; use a real Steam account with access to the dedicated server tool.
  • Storing a Steam password in .env is convenient but sensitive. Keep .env private.
  • If Steam Guard blocks the first automated login, prime the SteamCMD state once and then future reboot checks can reuse the cached session.

If you prefer not to use SteamCMD, you can still copy the Windows Steam Tools install into ./source/ manually and run the container that way.

One-time SteamCMD priming helper:

./scripts/prime-steamcmd.sh .

That opens SteamCMD inside the container with your persisted ./steamcmd directory. If Steam prompts for Steam Guard on first login, complete that once there and then exit. After that, normal docker compose up -d boots can reuse the saved SteamCMD state.

Editing the live server .env

For a real deployed instance, edit the instance .env, not the repo copy.

Example with a normal home-directory install:

nano /home/your-user/windrose-servers/19/.env

Or with the helper:

/home/your-user/windrose-dedicated/scripts/edit-steam-env.sh /home/your-user/windrose-servers/19

Then set:

STEAM_UPDATE_ON_BOOT=true
STEAM_USERNAME=your-steam-account
STEAM_PASSWORD=your-steam-password
STEAMCMD_APP_ID=4129620
STEAMCMD_PLATFORM=windows
STEAMCMD_VALIDATE=false

After saving, restart the instance:

cd /home/your-user/windrose-servers/19
docker compose up -d --force-recreate

Networking and joining

Windrose currently uses invite-code joining, not direct IP or domain joins.

  • Players join from the game client with Play -> Connect to Server -> <invite code>.
  • The official guide describes ports as dynamically assigned through NAT punch-through / UPnP.
  • Because of that, this repo uses network_mode: host and does not publish a fixed ports: list in Compose.
  • In practice, there is no official fixed default game port to front in this setup today.

If the networking model changes in a future Windrose build and the developer documents stable game/query ports, you can replace host networking with explicit port publishing at that time.

Invite code

There are two supported ways to get the invite code for a running server:

  1. Watch the startup console logs.
  2. Read ServerDescription.json after the server has started.

In this Docker layout, the recommended path is the helper script:

./scripts/show-invite-code.sh .

That script checks these files in order:

  1. runtime/R5/ServerDescription.json
  2. runtime/ServerDescription.json
  3. config/ServerDescription.json

This matters because the live game runtime may update its own server description during startup, and that runtime file is the best source of truth for the currently active invite code.

If you want to read it manually, the most authoritative file is usually:

jq -r '.ServerDescription_Persistent.InviteCode' ./runtime/R5/ServerDescription.json

If the helper script does not return a value yet, check the logs:

docker compose logs -f

Windrose prints a Server Connection Info block when registration succeeds, and the invite code shown there is what players should use in Play -> Connect to Server.

Config workflow

The guide identifies two important config files:

  • ServerDescription.json
  • WorldDescription.json

This project stores the admin-edited copies in config/. At container start:

  1. config/ServerDescription.json is merged into the runtime server description.
  2. config/WorldDescription.json is written into the selected saved world's versioned path.

On a brand new instance, Windrose creates its versioned RocksDB world directory during the first successful boot. That means world-setting changes are fully applied after the first startup has created a real path such as R5/Saved/SaveProfiles/Default/RocksDB/0.10.0/Worlds/<world id>/WorldDescription.json.

After first boot, the runtime-generated identity values are treated as authoritative. In practice, that means the sync script preserves or back-fills fields such as:

  • DeploymentId
  • PersistentServerId
  • the selected real WorldIslandId

This avoids stale placeholder IDs from an initial config overwriting the real saved world on later restarts.

P2pProxyAddress is the host address Windrose advertises to its P2P/ICE networking layer. 0.0.0.0 is fine as a generic bind address, but it may not be enough for clients on the same LAN or nearby network path that need a concrete host address to reach. If P2pProxyAddress is left as 0.0.0.0, the sync script attempts to auto-detect a usable host LAN IP and writes that into the runtime server description before launch.

The config files follow the official schema from the bundled DedicatedServer.md, including:

  • ServerDescription_Persistent
  • WorldDescription
  • WorldSettings.BoolParameters
  • WorldSettings.FloatParameters
  • WorldSettings.TagParameters

Host requirements

This repo includes a host bootstrap script:

sudo ./scripts/install-host-requirements.sh

It does these Linux-side setup steps for you:

  • installs docker.io
  • installs docker-compose-v2
  • installs unzip and jq
  • enables and starts Docker
  • creates source/, runtime/, config/, and logs/
  • creates steamcmd/
  • creates compose.yaml from compose.example.yaml if needed
  • copies .env.example to .env if needed

It does not download the actual Windrose dedicated-server files by itself. The dedicated server can be installed and updated automatically if SteamCMD is configured in .env, or you can still supply the files manually from the official Steam Tools install on Windows.

Optional host tuning

These are optional Linux VM recommendations, not Windrose-specific requirements.

On small game-server VMs, irqbalance can help by spreading hardware interrupts across available vCPUs instead of letting too much interrupt handling stay concentrated on one CPU. It is not a dramatic tuning change, but it is a sensible low-risk default on multi-core KVM or Proxmox guests.

Example install:

sudo apt-get update
sudo apt-get install -y irqbalance qemu-guest-agent
sudo systemctl enable --now irqbalance qemu-guest-agent

Conservative VM memory tuning can also be reasonable for game servers:

sudo tee /etc/sysctl.d/99-windrose-tuning.conf >/dev/null <<'EOF'
vm.swappiness=10
vm.vfs_cache_pressure=50
EOF
sudo sysctl --system

That keeps the guest less eager to swap and less aggressive about reclaiming cache. These are safe optional defaults, but they are not required for normal operation.

Importing a Windows zip on Linux

If you exported the Steam install as a zip from Windows:

unzip -q "Windrose Dedicated Server.zip" -d /tmp/windrose-import
mkdir -p ./source
cp -a /tmp/windrose-import/"Windrose Dedicated Server"/. ./source/

Expected root files include:

  • WindroseServer.exe
  • DedicatedServer.md
  • R5/Binaries/Win64/WindroseServer-Win64-Shipping.exe

The root WindroseServer.exe acts like a small wrapper. In Docker, the image uses the shipping binary directly:

R5/Binaries/Win64/WindroseServer-Win64-Shipping.exe -log

That keeps the runtime path closer to the successful manual Wine launch we validated on Ubuntu.

Notes and limitations

  • This is a compatibility workaround for a Windows-only dedicated server.
  • It is not an officially supported Linux runtime from the game developer.
  • Networking for Windrose appears to rely on NAT punch-through / UPnP according to the public guide, so the compose file uses network_mode: host on Linux instead of fixed published ports.
  • You should keep the copied server files up to date with the game client version.
  • GitHub's web UI sometimes briefly shows banners like Cannot retrieve latest commit at this time even when the repo and commits are fine. If the file tree is loading and git clone or git fetch still works, that is usually a transient GitHub-side web issue rather than a repo corruption issue.

About

Docker image for Windrose. Subject to change depending on updates from Windrose

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors