DEV Community

flpslv
flpslv

Posted on

Running WineHQ inside a docker container

(Before I waste any of your time, let me start by warning that this post was done using the nVidia gpu that's on my system. Unfortunately I don't have (yet) the means (nor time) to properly test this with an AMD or even Intel GPU.)

Alt Text

Well ...
Nowadays this isn't exactly true...
Nor this solution is the best.
But as I said in my profile, I'm just curious.

Things have evolved, tools like lutris or even steam's proton are providing easier and faster ways of playing games that do not have native linux ports.

I have tried both and things just work. It's a fact. And I was able to play the ( supported ) games I wanted without big effort:

  • With Lutris, I just had to go to the site and pull the pre-configured installer for the game I wanted and let it work it's magic.
  • With Steam Proton, I just had to download the game and play it.
  • With Wine itself, either installed by my distro package manager or compiled from source.

All of those, as I stated before just work perfectly (one way or another). But curiosity was still nagging: "Can I just have a container with all that stuff installed and not have all those dependencies dangling on my system?"

In fact, that question always comes back to me once in a while, when I think about how many stuff I have installed on my system and how many I really need on a daily basis. Even further, what will I miss when I change or need to reinstall my distro.

With that in mind, I started to experiment with containers. Even if only to test newer wine versions on a more or less clean way, because I've already upgraded to a newer version that unfortunately was broken, and had to reinstall the previous working version packages.

So, I decided to build things up from a Ubuntu Focal container and split in two containers:

  • My base container based on Ubuntu having only the nVidia drivers installed
  • My actual wine container with all the dependencies for the games.

I went for this approach since gpu drivers don't come out that often (unfortunately) and I can also have a base image to work with.

Before going into more detail, if you notice anything unusual with the Dockerfiles is because this build system is prepared to be used with Makefiles. But they also can be built normally with the traditional docker build cmd. The defaults can be overridden with --build-arg flags.

Also, make sure the driver version you have installed on your host are the same as the drivers installed inside the container.

Base container

ARG UBUNTU_VERSION=focal
ARG NVIDIA_DRIVER_VERSION=450
FROM ubuntu:$UBUNTU_VERSION

ARG UBUNTU_VERSION
ARG NVIDIA_DRIVER_VERSION

ENV DEBIAN_FRONTEND=noninteractive
ENV UBUNTU_VERSION=$UBUNTU_VERSION
ENV NVIDIA_DRIVER_VERSION=$NVIDIA_DRIVER_VERSION

ENV PKGNV="nvidia-driver-${NVIDIA_DRIVER_VERSION} libnvidia-gl-${NVIDIA_DRIVER_VERSION}:i386"
ENV PKGVK="libvulkan1 libvulkan1:i386 vulkan-tools vulkan-utils"


RUN     dpkg --add-architecture i386 && \
    apt update && \
    apt install -y gnupg2 apt-transport-https curl 

RUN apt install -y $PKGNV $PKGVK

So, this is the first container, based on a ubuntu focal version and nvidia drivers 450 (by default), vulkan and a few base tools to be used later. I will refer to it for the next steps as ubuntu-nvidia-vlk:focal.

Wine container

FROM ubuntu-nvidia-vlk:focal

ARG WVERSION="5.17"
ARG WTVERSION="20200412"

ENV WINE_VERSION="${WVERSION}"
ENV PKG_WINE_VERSION="${WVERSION}~${UBUNTU_VERSION}"
ENV WINE_TRICKS_VERSION="${WTVERSION}"

RUN curl -s https://dl.winehq.org/wine-builds/winehq.key | apt-key add - && \
    echo "deb https://dl.winehq.org/wine-builds/ubuntu/ ${UBUNTU_VERSION} main" | tee /etc/apt/sources.list.d/wine.list && \
    apt update && \
    apt install -y winbind cabextract wget fonts-wine ttf-mscorefonts-installer\
        winehq-staging=$PKG_WINE_VERSION \
        wine-staging=$PKG_WINE_VERSION \
        wine-staging-i386=$PKG_WINE_VERSION \
        wine-staging-amd64=$PKG_WINE_VERSION

ADD https://github.com/Winetricks/winetricks/archive/${WINE_TRICKS_VERSION}.zip /tmp/wt.zip 
RUN unzip /tmp/wt.zip -d /tmp/ && \
    cp /tmp/winetricks-${WINE_TRICKS_VERSION}/src/winetricks /usr/local/bin && \
    rm -Rf /tmp/*

And we can call this our winehq:5.17 container.

So, we have a base container and a wine container.
Since most of the games will ask for winbind and cabextract I've decided to put those together with wine. Also, some font packages since they're used a lot by launchers/loaders.
I guess I could have another stage just for those needed packages, but I didn't have the time yet to figure out the best stage to put them.
So now we only need to create a script to use the containers to avoid having to memorize a huge command line.

At this point, we could also add dxvk to our container by adding this lines to the dockerfile

ARG DXVKVERSION="1.7.1"
ENV DXVK_VERSION="${DXVK_VERSION}"
ADD https://github.com/doitsujin/dxvk/releases/download/v${DXVK_VERSION}/dxvk-${DXVK_VERSION}.tar.gz /tmp/dxvk.tar.gz 

... but DXVK can also be installed by winetricks. So, I'll just go with winetricks method for now.

We can add this script to one of the PATH directories so we can start the container at any time.

Also, it's just a personal preference to have all the versions as environment variables on my container, so I can quickly check them any time I need.

It will create a temporary folder on your $HOME/tmp path and use it as $HOME inside the container. Also, the current path which will also be the WINEPREFIX will be placed inside the container on $HOME/game.

I had to do it this way, because the .Xauthority file will be mounted in the home dir and since it's a volume two things could happen:
1 - if I made the WINEPREFIX my home dir, the .Xauthority file would end up being copied to that location on the host.
2 - if the home dir didn't exist, it would be created by the user root (the container default user) which could lead to lack of permissions for some apps that use the home dir as default location to write theyr own files ( winetricks, for example).

#!/bin/bash

curdir="$(pwd)"
vhome="${HOME}/tmp/.winestorage"

if [ ! -d "$vhome" ]; then
    echo "creating $vhome"
    mkdir -p $vhome
fi

docker run --rm \
    --name wine \
    -u $(id -u):$(id -g) \
    -e DISPLAY \
    -e WINEPREFIX=/wine/game \
    -v /tmp/.X11-unix:/tmp/.X11-unix \
    -e PULSE_SERVER=unix:/pulse \
    -e HOME=/wine \
    -v $HOME/.Xauthority:/wine/.Xauthority \
    -v /run/user/$(id -u)/pulse/native:/pulse \
    --device /dev/nvidia0:/dev/nvidia0 \
    --device /dev/nvidiactl:/dev/nvidiactl \
    --device /dev/nvidia-uvm:/dev/nvidia-uvm \
    --device /dev/nvidia-uvm-tools:/dev/nvidia-uvm-tools \
    --device /dev/nvidia-modeset:/dev/nvidia-modeset \
    -v ${vhome}:/wine \
    -v ${curdir}:/wine/game \
    -ti \
    winehq:5.17 \
    bash

There are several considerations to this script:

  • it will be removed after termination --rm
  • it will map my user and group inside the container -u $(id -u):$(id -g)
  • it will set the WINEPREFIX env to /wine/game -e WINEPREFIX=/wine/game and set my HOME to /wine -e HOME=/wine
  • it will mount the directory containing the X11 sockets inside the container -v /tmp/.X11-unix:/tmp/.X11-unix
  • since I'm using pulesaudio it will define the socket path -e PULSE_SERVER=unix:/pulse and it will place my actual socket inside the container -v /run/user/$(id -u)/pulse/native:/pulse
  • it will mount my .Xauthority file on the user's home directory -v $HOME/.Xauthority:/wine/.Xauthority
  • add all my devices corresponding to the nVidia gpu
  • and finally mount the home and game paths in the container

We can also extend this script to pass extra wine variables to the container.

Now, inside the container we can just go directly to /wine/game and start installing or playing what we want to run.

When trying to install something that needs extra packages installed, there is also the possibility of opening a new shell, enter the container as root and install what we need just by running docker exec -u root -ti wine bash.

Using wine isn't really the target of this post, but there is enough information around the internet to help with that. And having winetricks installed is also a nice aid to configure prefixes.
One of the motives I had, using that directory structure (and the $vhome path) was also to try to maintain some cache for winetricks downloads.

I've tried this method to launch Battle.net and try a couple of games which worked surprisingly well.

I was also able to run Epic Games Store, but unfortunately had no luck when trying to play the few games I have there. I guess some things were missing on my prefix.

And finally, was able to install Wargaming's Game Center and play a few rounds of world of tanks.

At this point, most of the needed work is just figuring out which dependencies we need in our prefix and which wine libraries we need to override. Lutris installation scripts are nice to look for more information on a given game.
Some errors still occur, but the wine debug messages can also help to trace some problems and find the possible missing packages we might need to install.

Security Concerns

I'm not going to dive deep into this approach security flaws.
Ari Kalfus already did it very well on his post Running a Graphical Application from a Docker Container - Simply and Securely. It's worth the read even if you're not thinking on running any GUI applications.

So, let's just try to tweak a bit our launcher script to address some of those valid points:

#!/bin/bash

curdir="$(pwd)"
vhome="${HOME}/tmp/.winestorage"

if [ ! -d "$vhome" ]; then
    echo "creating $vhome"
    mkdir -p $vhome
fi

docker run --rm \
    --cap-drop=all \
    --name wine \
    -u $(id -u):$(id -g) \
    -e DISPLAY \
    -e WINEPREFIX=/wine/game \
    -v /tmp/.X11-unix:/tmp/.X11-unix:ro \
    -e PULSE_SERVER=unix:/pulse \
    -e HOME=/wine \
    -v $HOME/.Xauthority:/wine/.Xauthority:ro \
    -v /run/user/$(id -u)/pulse/native:/pulse \
    --device /dev/nvidia0:/dev/nvidia0 \
    --device /dev/nvidiactl:/dev/nvidiactl \
    --device /dev/nvidia-uvm:/dev/nvidia-uvm \
    --device /dev/nvidia-uvm-tools:/dev/nvidia-uvm-tools \
    --device /dev/nvidia-modeset:/dev/nvidia-modeset \
    -v ${vhome}:/wine \
    -v ${curdir}:/wine/game \
    -ti \
    winehq:5.17 \
    bash

Bear in mind that one of the things that stops working when we add the --cap-drop=all is the possibility to enter the container as root and change anything on the container system.

Of course there are easier ways to play games on linux, but having this working is a nice thing for me, so I can get rid of many unwanted installed packages on my host system.

I could improve this post on trying to get this working on AMD or Intel graphics, but it's something I won't be able to do for now.

Top comments (0)