Running Valheim dedicated server on Windows Containers

 

Ah, Valheim. Minecraft for men.

As we know, you can run Valheim servers in multitude of ways - the game client itself can be a server for your friends, or you can set up a Linux or Windows dedicated server (installed with STEAMCMD) and run it either natively or on a virtual machine, or you can even have a nice pre-set container Linux version of the server from https://github.com/lloesche/valheim-server-docker

But alas! Windows users have no container option - or at least, not yet.

A few words about Lloesches Valheim Docker Server

Lloesches version of dedicated server in a Docker container is quite well thought out - it runs the server in a supervised manner (restarts the software if the server crashes, has watchdog functions and so on), updates the game code from steam periodically and supports all kinds of mods. I have no idea how to easily implement all that functionality in a Windows container, so if you want something robust and kitted out, install Ubuntu, Docker and run it.

My approach is a bit more academic - I just wanted to see if running the dedicated server on a Windows container was possible. We know that running the dedicated server on a full-fat Windows VM definitely is possible, but how about running it as a pure console application on something leaner - like Windows Server Core?

As we all know, nanoserver is also available from Microsoft, but to be frank, the limitations on nanoserver are so severe that I did not even try - it MIGHT be possible with a lot of tweaking, but I have a feeling it will be near impossible a task. I would love to be proven wrong, though.

So Windows Server Core it is, then.

Prequisites

We're using Windows Server 2022 as a starting point. Windows Server 2019 should work as well without any modifications to the things we're about to do here. So pick your poison, just remember that a container needs the same kernel to be present on the image and the hosting "real" server - so no mix and match of versions.

You'll need to install a Windows Server (preferrably with desktop experience - and this will be important later) and Windows Containers role on it. There are multiple container engine options, I went with Docker CE. This page is not a tutorial on how to install Docker on Windows, so go and make it work - when you can pull images from the internet, change them and commit new images you're ready to continue reading this. Go, take your time, I'll wait.

valheim quote

Image and .NET 3.5

First we need to pull the LTSC (long term servicing channel) Server Core image from Microsoft. Still no tutorials here, you should know how to do it by now. I selected the version with KB5022291 update on it (ltsc2022-KB5022291)-

I'm not absolutely sure if we need the .NET 3.5 installed on the image, but I decided to go the safe route and install it anyway. The Server Core 2019 and 2022 have .NET 4 and above already installed, but it cannot hurt to have some downlevel compatibility.

To kick things off, we run the container in the interactive mode (-it flag) so that we can actually, you know, interact with it.

docker run -it mcr.microsoft.com/windows/servercore:ltsc2022-KB5022291

When the console (CMD) appears, start PowerShell (by typing "powershell" and pressing enter. Why am I writing this?). When PowerShell runs, install the .NET windows feature:

PS C:\> Install-WindowsFeature net-framework-core

When the feature has been installed, exit the PowerShell by typing 'exit' and exit the container alltogether by typing... you guessed it, 'exit'.

Now it is time to commit our container to a new image, we'll call it 'ltsc2022-net35' - first find the container id (I had 'a5db0388404d', yours will be different. Why, again, am I writing this? You should know this by now) with 'docker ps -a', copy the id and:

PS C:\Users\Administrator> docker commit a5db0388404d ltsc2022-net35

Now we have a relatively clean version of Windows Server 2022, with patches and .NET 3.5 installed which we can come back to should we screw something up.

On with the show, then.

STEAMCMD

The Valheim dedicated server is installed with STEAMCMD command line program. STEAMCMD needs to get into our container somehow. You could copy the installer package from your local filesystem to the image with docker cp command, but we want to live dangerously and get all kinds of stuff on to our image straight from the internet.

First, we spin up a new container based on our glorious .net-enabled servercore:

docker run -it ltsc2022-net35

Sure hope you got your network sorted, since now we download STEAMCMD from the running container (yes, once again, start PowerShell first once the container spins up):

PS C:\> Invoke-WebRequest https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip -UseBasicParsing -OutFile steamcmd.zip

This downloads the zipped version of steamcmd. The -UseBasicParsing is needed since there is no initial configration for the web browser component done in the container. Just sayin'.

We need to extract it into a directory (no matter what it is called, just choose one, I went with 'steam' under the root of the drive):

PS C:\> Expand-Archive .\steamcmd.zip C:\steam\

Once the package has been extracted you can go ahead and delete that unsightly .zip file from your drives root. Move into the steam directory and let's install some Valheim goodness!

steamcmd.exe +force_install_dir "C:\Valheim" +login anonymous +app_update 896660 -beta none validate +quit

Yes, those are plus signs. Yes, we're forcing the installation directory to be 'C:\Valheim'. Yes, the dedicated server does not need logging into a steam account, anonymous as a user works fine. The app Steam ID is 896660. We're not subscribing into a beta channel.

After the STEAMCMD does its thing, we actually have Valheim dedicated server with all the trimmings now in the C:\Valheim directory. But does it work?

And that's when things went very, very wrong.

In the Valheim directory there is a ready-made batch file for starting the dedicated server. Try it. I dare you. It should do at least something, right? No matter that we have no configuration or world files or any of that stuff.

PS C:\Valheim>./start_headless_server.bat
PS C:\Valheim>

(insert confused silence here.)

No starting up messages. No errors. Nothing.

The dedicated server has a log file, right? The batch file has valheim_server.exe command with a lot of options after it, so it puts the log file somewhere, right? After a bit of searching we find no log file anywhere. Ok, so maybe it just crashes, so Get-EventLog to the rescue!

Nothing. Absolutely nothing in the Application or System event log. The server executable just exits immediatelly without any trace of anything going wrong. Ye Gods of Windows, hear my plea.

Mark Russinovich "When in doubt, run Process Monitor!" - Mark Russinovich

Installing and running the GUI version of Process Monitor on the container is impossible. We could run CLI versions but without text editor (oh believe me, we will come back to this later) there would be a lot of work to find out what went wrong. So what can we do?

We can run the dedicated server on a real full-GUI version of Windows Server (which we installed as our base system) and see what the valheim_server.exe does when it starts up.

Long story short - the executable is trying to load two OpenGL DLLs (opengl32.dll and glu32.dll) when it starts up, which are not present on a Server Core installation. And now you know why we installed a Desktop Experence version of Windows Server as our base system.

Leave the container window running and open another admin CMD on your system. Find the container image id with 'docker ps -A' and use the ID in the following commands, which copy the missing DLLs from the base OS to the image:

C:\Users\Administrator>docker cp C:\windows\System32\opengl32.dll ee78f55e3a7e:c:\windows\system32\
C:\Users\Administrator>docker cp C:\windows\System32\glu32.dll ee78f55e3a7e:c:\windows\system32\

Guess what? The dedicated server executable starts without a hitch.

But I want a text editor so I can modify files on the container

Modifying the startup batch file is cumbersome if you do it by copying files from host OS to the container. You would like to have a text editor on the container console. Server Core has no GUI, and even when there is some GUI shenanigans on a VM or bare metal Server Core installation (and you can run Notepad on it), there is no such thing on pure character-based container.

Luckily such editor exists, and it is called Micro. There are couple of ways to get the editor on the container:

The choice is yours. Link to the GitHub page is https://micro-editor.github.io/

When you have the micro.exe on the system, I recommend that you move it into the

C:\Users\ContainerAdministrator\AppData\Local\Microsoft\WindowsApps

folder, as this is present in the default PATH variable of the container - so you can use the editor from any folder without modifying the container PATH variables. Only the executable is needed, you can discard all the other stuff in the zip archive.

Using the editor is just like any other editor: "micro whatever.txt" will open the whatever.txt file. Saving is done by CTRL-S and CTRL-Q exits the editor.

Game data files (world files) and logs

So where does the Valheim world data go, I hear you say. As containers are ephemeral in their nature, world data inside the container would be lost the instant the container exits. So we need a place on the host system to store the persistent world (and even import existing worlds from elsewhere).

Lets make some assumptions first:

So go ahead, create the real directories on the host as above and on your container. Just mkdir some folders, the binding comes later.

So now what we have is a running container which has:

Looking good. Time to make one last change - the startup batch file inside the container comes from steam, and later updates will overwrite it. So make a copy:

PS C:\Valheim> copy start_headless_server.bat start.bat

And now open the new "start.bat" in micro editor and let us do stuff reflecting our naming decisions:

@echo off
set SteamAppId=892970

echo "Starting server PRESS CTRL-C to exit"

REM NOTE: You need to make sure the ports 2456-2458 is being forwarded to your server through your local router & firewall.
valheim_server -nographics -batchmode -name "BestValheimServer" -port 2456 -world "MyValheimWorld" -password "password123" ^
-savedir "C:\valheimdata" -public 0 -logFile "C:\valheimlogs\log.txt"

(the caret ^ is present for readability here. It allows for multiline commands inside batch files. You may remove it and put everything on a single line.)

Save the file.

Now we need to commit all this into the image. Exit your container and create a new commit, let's call it '2022-valheim' (once again, use docker ps -A to find the image id and commit it to a new name).

Running the container

So now we have our "2022-valheim" image somewhere in the bowels of docker. Time to run it for the first time:

docker run -it --name valheim-server -p 2456-2457:2456-2457/udp -v C:/Users/Administrator/valheimlogs:c:/valheimlogs -v C:/Users/Administrator/valheimdata:c:/valheimdata 2022-valheim

the -v arguments bind mount our real host OS directories to the container directories. When the container starts, you can navigate to the C:\Valheim directory and type "start.bat". The server should start, you should see the log file in the host filesystem logs directory and the fresh databases in the database directory.

NOTE THE FORWARD SLASHES IN THE PATH NAMES. Oh Docker, you so crazy.

The container will open the two ports needed for the communication, and by default the container runs in a NAT network with the ports destination NATted in to the container.

So, open your Valheim game, and join game on the host server ip - the ports will be forwarded to the container and you should see password prompt, and things should work.

Next steps

Yes, the container still runs in interactive mode. You can now modify the docker environement with ENTRYPOINT or CMD values that the server runs automatically when the container is spun up.

You can edit the startup.bat to include environment variables for the server name, port numbers, password, world name and log location, so that you pass these values with docker run command and run multiple containers without hard-coded settings (like the Lloesche server does).

You create another startup batch file which will run the STEAMCMD first and update the server files on every container spin-up.

You can create a scheduled task to kill the server periodically and perform the update, just like the Lloesche server does.

Go do stuff.