Run Windows with Docker Compose & KVM

Hello everyone,
I would like to share a tutorial with you which I wrote for the german linux forum https://forum.linuxguides.de/.

I hope the translation is reasonably readable.
Big thanks to deepl :joy:


Theory

Motivation

We use the Docker repository from this GitHub page for our project: https://github.com/dockur/windows
And that’s exactly where all the credits are and my thanks go to this great project.
Personally, the documentation was too poor for me and I had a lot of try & error moments.
Now I’ve got the hang of it and would like to share my newly acquired knowledge and experience with you.
Hopefully you will find it easier to get started than I did :sweat_smile:

Requirements

  • Any Linux as host system
  • CPU with KVM support
  • Installed Docker and Docker Compose
  • Internet connection
  • Web browser (tested Firefox and Brave)
  • Terminal
  • Optional: RDP client
  • Optional: Hard disk or partition with NTFS or FATx
  • Approx. 15-20 minutes + download and installation time

Check KVM support

lscpu | grep Virt  

The output should either be Virtualization: VT-x for Intel CPUs or Virtualization: AMD-SVM.
If this is not the case, check whether you have activated the function in the BIOS.

What this tutorial shows you

This tutorial explains step by step how to install an “arbitrary” Windows version in a Docker container.
Once as a so-called unattended installation, i.e. completely automatic, and once as a normal, manual installation.
We will also

  • Integrate several virtual hard disks
  • Implement USB stick forwarding
  • Share files with the host
  • Pass-through of an NTFS or FAT partition

What this tutorial will not show you

You will not learn how to install Docker and the required components, nor what Docker is and how to use it.


Basic concept

It is as simple as it is ingenious: With the help of a single configuration file (YAML format), we define our Docker container.

Structure of a .YML file

The following is the simplest variant for a configuration file.

services:
  windows:
    image: dockurr/windows
    container_name: windows
    devices:
      - /dev/kvm
    cap_add:
      - NET_ADMIN
    ports:
      - 8006:8006
      - 3389:3389/tcp
      - 3389:3389/udp
    stop_grace_period: 2m
    restart: on-failure
    environment:
      VERSION: "win11"
      RAM_SIZE: "4G"
      CPU_CORES: "4"
      DISK_SIZE: "64G"
    volumes:
      - /var/win:/storage

The “image” parameter tells Docker which image it is and where it is located for the download.
“container_name” is simply the name we want to give our new container and is purely cosmetic.

Ports

In the Ports section, the ports for VNC (8006) and RDP (3389) are specified and ready to use.

Enviroment

The parameters and Enviroment and Volumes are the most interesting for us.
Under Enviroment we specify the Windows version to be installed (see table in the appendix).
The rest should be self-explanatory:
RAM_SIZE determines how much memory we want to give the container from our physical RAM.

The same applies to CPU_CORES, i.e. how many cores of its own CPU the container can use.
With DISK_SIZE we specify how large the virtual hard disk should be.

Volumes

/var/win:/storage
/var/win is simply the path where we want to save our virtual hard disk. The path can be customized as desired.

The storage parameter simply says that it is a storage device.


Practice Part 1 - (Automatic) Installation

Enough theory for now, let’s start our first Linux > Docker > Windows container!

Step 1

  1. create a directory where the container should be stored (e.g. /home/docker)
  2. create a file with the name docker-compose.yml in the same directory
  3. copy the following code (version 1 or version 2) into the file and customize it according to your wishes.

Version 1

services:
  windows:
    image: dockurr/windows
    container_name: windows
    devices:
      - /dev/kvm
    cap_add:
      - NET_ADMIN
    ports:
      - 8006:8006
      - 3389:3389/tcp
      - 3389:3389/udp
    stop_grace_period: 2m
    restart: on-failure
    environment:
      VERSION: "win11"
      RAM_SIZE: "4G"
      CPU_CORES: "4"
      DISK_SIZE: "64G"
    volumes:
      - /var/win:/storage

Version 2

Version 2 extends version 1 with some settings for the automatic installation.

I have added the following values under Enviroment:

LANGUAGE: "German"
REGION: "de-DE"
KEYBOARD: "de-DE"
USERNAME: "tux"
PASSWORD: "tux"

I think the values are self-explanatory but are explained in more detail in the appendix.

services:
  windows:
    image: dockurr/windows
    container_name: windows
    devices:
      - /dev/kvm
    cap_add:
      - NET_ADMIN
    ports:
      - 8006:8006
      - 3389:3389/tcp
      - 3389:3389/udp
    stop_grace_period: 2m
    restart: on-failure
    environment:
      VERSION: "win11"
      RAM_SIZE: "4G"
      CPU_CORES: "4"
      DISK_SIZE: "64G"
      LANGUAGE: "German"
      REGION: "de-DE"
      KEYBOARD: "de-DE"
      USERNAME: "tux"
      PASSWORD: "tux"
    volumes:
      - /var/win:/storage

Step 2

  1. start terminal and change to the created directory
  2. start container with
docker compose up

# or with output

docker compose up -d

The new container can be accessed directly via VNC using the URL http://localhost:8006/.

Now wait until the ISO has been downloaded and the automatic installation has been completed.

Step 3

Once the container is ready or the installation is complete, the “fresh” Windows can be used.

Alternatively, you can also use an RDP connection via localhost:3389. My recommendation is clearly RDP. It simply feels smoother.


Practice part 2 - Creating space

In the second practical part, we add another virtual hard disk to our container and connect the container to a USB stick.

Step 1 - USB stick

The assignment is made close to the hardware - i.e. specifically for each individual device.

In the Enviroment section, we need a new entry to assign a USB stick to the container.

ARGUMENTS: "-device usb-host,vendorid=0x090c,productid=0x1000"

The vendor and product ID of the device is required. To get these two values, we need the command lsusb.

Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 0bda:5420 Realtek Semiconductor Corp. 4-Port USB 2.0 Hub
Bus 001 Device 003: ID 05e3:0610 Genesys Logic, Inc. Hub
Bus 001 Device 004: ID 046d:0843 Logitech, Inc. Webcam C930e
Bus 001 Device 005: ID 08bb:2902 Texas Instruments PCM2902 Audio Codec
Bus 001 Device 006: ID 090c:1000 Silicon Motion, Inc. - Taiwan (formerly Feiya Technology Corp.) Flash Drive
Bus 001 Device 007: ID 046a:0115 CHERRY CHERRY Wireless Device
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 002 Device 002: ID 0bda:0420 Realtek Semiconductor Corp. 4-Port USB 3.0 Hub
Bus 002 Device 003: ID 05e3:0626 Genesys Logic, Inc. Hub
Bus 002 Device 004: ID 0bda:8153 Realtek Semiconductor Corp. RTL8153 Gigabit Ethernet Adapter

In my case, the USB stick is Device 006 - recognizable by the name Flash Drive.
The ID section contains the vendor and product ID in the order (separated by a :).
We place a 0x in front of each of these values >> vendorid=0x090c,productid=0x1000.
Finally, the following parameter must be added in the Devices section:

- /dev/bus/usb

The container can be started and the USB stick can be used normally.

Step 2 - Additional partitions

Again we start in the Enviroment section and expand it as follows:

DISK2_SIZE: "10G"
DISK3_SIZE: "2G"

We add the following to the Volumes section:

    volumes:
      - /var/win:/storage
      - /var/win:/storage2
      - /var/win:/storage3

The size and storage location of the new virtual hard disks can of course be freely adjusted.
After the container has started up, the new virtual hard disks still need to be formatted.


Practice Part 3 - Special Operations

Part 1 - Manual Windows installation

If you do not want an automatic installation, you can also trigger a manual installation with a single parameter.

environment:
  MANUAL: "Y"

Part 2 - Sharing files with the host system

For me, one of the most important points is exchanging files between the container and the host system.

In the Volumes section, only one additional parameter is required: - /home/toadie/share:/shared

    volumes:
        - /var/win:/storage
        - /home/toadie/share:/shared

The host.lan device appears under Network - the “Exchange” directory can be found here.

Part 3 - Pass-Through

WARNING:

I can’t say what will happen if you use a Linux partition that already contains data!

My recommendation: Shrink a partition and create a NTFS or FATx partition.


Pass-through** is a technique for using devices from the host system directly in a VM or Contrainer. In other words, devices are not emulated but the existing hardware is addressed directly as by the host system. Of course, this offers significantly better performance.

Only one parameter needs to be added to the Devices section:

- /dev/sdb2:/disk2

In this example, the container would have direct access to the 2nd hard disk and its 2nd partition.

Alternatively, the entire hard disk can of course be used.

Part 4 - Own ISO for the installation

To use your own Windows ISO, the Volumes section is changed as follows:

    volumes:
        - /var/win/win.iso:/win.iso
        - /var/win:/storage

Conclusion and final words

Performance

I doubt that it is possible to play games that are newer than 1999 with such a system - if at all.
Docker is not worthwhile for gamers.

For smaller services or programs, however, a Windows Docker is definitely an alternative to a virtual machine.

I was amazed at the performance when converting audio files. Here, the Docker variant is clearly ahead of Virtual Box and QEMU.

Especially when using Pass-Through there is nothing to complain about.

Furthermore, I have the, perhaps subjective, impression that the Docker Windows containers put less strain on the host system overall than conventional virtual machines.

Recommendations

For small requirements, the Tiny version should be sufficient. This requires significantly less hard disk capacity than full Windows versions. I myself have opted for the Tiny version. So far, it has been completely sufficient for my few usage scenarios.

Outlook

I cannot say whether all images offer the same features and work equally well.

For my tests I used win11, win10, win7 and tiny10. All without any problems. However, tiny10 did not accept any changes to the language - but this is probably due to the concept of a Tiny Windows.

Appendix

Available Windows versions

Conclusion and final words

Performance

I doubt that it is possible to play games that are newer than 1999 with such a system - if at all.
Docker is not worthwhile for gamers.

For smaller services or programs, however, a Windows Docker is definitely an alternative to a virtual machine.

I was amazed at the performance when converting audio files. Here, the Docker variant is clearly ahead of Virtual Box and QEMU.

Especially when using Pass-Through there is nothing to complain about.

Furthermore, I have the, perhaps subjective, impression that the Docker Windows containers put less strain on the host system overall than conventional virtual machines.

Recommendations

For small requirements, the Tiny version should be sufficient. This requires significantly less hard disk capacity than full Windows versions. I myself have opted for the Tiny version. So far, it has been completely sufficient for my few usage scenarios.

Outlook

I cannot say whether all images offer the same features and work equally well.

For my tests I used win11, win10, win7 and tiny10. All without any problems. However, tiny10 did not accept any changes to the language - but this is probably due to the concept of a Tiny Windows.

Appendix

Available Windows versions

Value for Docker Version Download size
win11 Windows 11 Pro 6.4 GB
win11e Windows 11 Enterprise 5.8 GB
win10 Windows 10 Pro 5.7 GB
ltsc10 Windows 10 LTSC 4.6 GB
win10e Windows 10 Enterprise 5.2 GB
win8 Windows 8.1 Pro 4.0 GB
win8e Windows 8.1 Enterprise 3.7 GB
win7 Windows 7 Enterprise 3.0 GB
vista Windows Vista Enterprise 3.0 GB
winxp Windows XP Professional 0.6 GB
2022 Windows Server 2022 4.7 GB
2019 Windows Server 2019 5.3 GB
2016 Windows Server 2016 6.5 GB
2012 Windows Server 2012 4.3 GB
2008 Windows Server 2008 3.0 GB
core11 Tiny 11 Core 2.1 GB
tiny11 Tiny 11 3.8 GB
tiny10 Tiny 10 3.6 GB

Parameters

Devices

Parameters Description
/dev/kvm
/dev/bus/usb
/dev/sdb:/disk2

Volumes

Parameters Description
- /home/tux/docker/win10:/storage Path for the virtual Docker disk
- /home/tux/docker/share:/shared Path for a share directory to exchange files with between host and container

Enviroment

Parameters Description
VERSION Windows version - see table Available Windows versions
RAM_SIZE Size of the available physical memory
CPU_CORES Number of CPU cores provided
DISK_SIZE Size of the virtual hard disk
LANGUAGE Desired language, e.g. German
REGION Time zone etc., e.g. de-DE
KEYBOARD Keyboard layout, e.g. de-DE for the German keyboard layout
USERNAME User name for the Windows user, e.g. Tux
PASSWORD Password for the Windows user
ARGUMENTS e.g. for USB devices
MANUAL A manual installation is triggered (MANUAL: “Y”)
1 Like

Thanks for sharing @toadie

@hydn you’re welcome. Hope it can be useful

1 Like

@toadie
Very nice! I love using Docker composer, I’ll give a look

2 Likes