This document outlines the setup process for running Windows virtual machines using QEMU and Dockur within a Kasm Workspaces environment. This custom configuration allows for persistent or non-persistent Windows VMs accessible via Kasm.
Before configuring Kasm, prepare the host system where Kasm is running:
-
Create Shared Directories: These directories will store persistent VM data and shared OEM customization files.
# Main directory for shared data sudo mkdir -p /mnt/kasm_user_share # Example sub-directory for a specific Windows 11 VM's persistent disk sudo mkdir -p /mnt/kasm_user_share/w11 # Directory for OEM customization scripts and files sudo mkdir -p /mnt/kasm_user_share/oem
-
Set Ownership: Ensure the Kasm user (typically UID/GID 1000) has ownership of the shared directories.
sudo chown -R 1000:1000 /mnt/kasm_user_share/
-
Copy OEM Files: Copy the
oemdirectory from this repository source to the host path created above. This directory contains customization scripts run during the Windows installation within the container.# Example: Assuming you are in the root of this git repository sudo cp -r ./oem /mnt/kasm_user_share/- Note: The default
install.batwithin theoemfolder is configured to change the Windows RDP port to3366(as Kasm uses3389), add a corresponding firewall rule, and restart the RDP service.
- Note: The default
Build the custom Docker image that includes QEMU, Dockur, and the necessary startup scripts:
# Navigate to the directory containing the Dockerfile
cd /path/to/your/git/repo
# Build the image
sudo docker build -t qemu-dockur:latest -f Dockerfile .- (Optional but recommended): Consider pushing this image to a private registry accessible by your Kasm deployment for easier management.
Create a new Workspace in your Kasm UI with the following settings:
-
Docker Image:
qemu-dockur:latest(or the name/tag you used if different, potentially including your private registry prefix).
-
Volume Mappings:
- Map the host directories created earlier into the container. This example maps the specific
w11directory for persistent storage. See the "Recommendations" section for non-persistent usage.
{ "/mnt/kasm_user_share/w11": { "bind": "/storage", "mode": "rw", "uid": 1000, "gid": 1000, "required": true, "skip_check": false }, "/mnt/kasm_user_share/oem": { "bind": "/oem", "mode": "rw", "uid": 1000, "gid": 1000, "required": true, "skip_check": false } } - Map the host directories created earlier into the container. This example maps the specific
-
Docker Run Config Override:
- Apply necessary privileges, device access, and environment variables.
{ "hostname": "kasm", "user": "root", "privileged": true, "devices": [ "/dev/kvm", "/dev/net/tun" ], "cap_add": [ "NET_ADMIN" ], "environment": { "CPU_CORES": 4, "RAM_SIZE": "8G", "DISK_SIZE": "64G", "BOOT_MODE": "windows_secure", "VERSION": "11", "HOST_PORTS": "443,80", "QEMUDISPLAY": "vnc", "DISK_TYPE": "scsi", "DISK_FMT": "qcow2", "DISK_IO": "threads", "DISK_CACHE": "writeback", "DEBUG": "Y", "RDPFULLSCREEN": "true", "NOAUDIORDP": "true" }, "ports": { "3366": 3366, "8006": 8006 } }- Important Resource Limits: Ensure
CPU_CORESandRAM_SIZEdo not exceed the limits defined for the Kasm Workspace Agent/Pool. - Key Environment Variables:
VERSION: Specifies the Windows version Dockur should download (e.g., "11", "10", "2022"). See original Dockur docs below for options.QEMUDISPLAY: Set tovnc(recommended for install) orweb.RDPFULLSCREEN: See "Recommendations".NOAUDIORDP: See "Recommendations".
- Launch a new session using the configured Kasm Workspace.
- Once the Kasm desktop loads, double-click the
run.shscript on the desktop. - A terminal window will appear, initiating the Dockur process (downloading the Windows ISO, preparing storage).
- If
QEMUDISPLAYis set tovnc, a QEMU window will appear within Kasm, showing the Windows installation progress. If set toweb, you can access the installer via the web interface on port8006within Kasm. - The installation should proceed automatically using the settings and any
oemcustomizations. - RDP Autostart: If
RDPFULLSCREENistrueand theoem/install.batran successfully (configuring RDP on port 3366), thecustom_startup.shscript will attempt to automatically launch a fullscreenrdesktopsession once the VM is installed and reachable. - Troubleshooting Boot Hang: If the process seems stuck after creating the ISO but before Windows setup begins, try restarting the Kasm session. This often resolves timing issues.
- Installation Display (
QEMUDISPLAY): Usingvncis generally recommended during the initial Windows installation, as it provides a direct view of the QEMU console within the Kasm session, making troubleshooting easier. You can switch toweblater if preferred. - Persistent vs. Non-Persistent Storage:
- Persistent: Map a specific host directory (e.g.,
/mnt/kasm_user_share/w11) to/storageinside the container (as shown in the example). Changes made inside the VM will persist across Kasm sessions. - Non-Persistent (Snapshot Mode): To start with a fresh Windows image every time, change the volume mapping target from
/storageto/kasm_user_share. Thecustom_startup.shscript will detect this and automatically create a QCOW2 overlay image based on the disk image in/kasm_user_sharewhen the session starts. This overlay is stored in the container's ephemeral/storageand is discarded when the session ends.
- Persistent: Map a specific host directory (e.g.,
- Automatic Fullscreen RDP (
RDPFULLSCREEN):- Set to
"true"to enable the behavior wherecustom_startup.shwaits for the VM to boot and respond to pings, then automatically launchesrdesktopin fullscreen mode. Requiresrdesktopto be installed in the container and the VM's RDP to be correctly configured (e.g., byoem/install.bat). - Set to
"false"(or omit) to disable this automatic connection. You can connect manually using the Remmina shortcut placed on the Kasm desktop or your preferred RDP client.
- Set to
- Audio Handling & Startup (
NOAUDIORDP):- Problem: Kasm typically requires user interaction (like a click) within the session window before it fully establishes the audio stream back to the browser. If QEMU starts before this interaction happens, the VM might not have access to the correct audio sink, resulting in no sound.
"true": QEMU starts automatically in the background when the Kasm session loads. This is convenient but will likely result in no audio within the VM or Kasm session unless you manually interact very quickly. Choose this if you don't need audio or prefer the automatic RDP connection without interaction."false"(or omit): Thecustom_startup.shscript will wait for PulseAudio and then open a terminal window prompting you to press Enter. This pause allows you time to click inside the main Kasm window (activating the audio stream) before pressing Enter in the terminal to start QEMU. This is the recommended setting if you need audio within the Kasm session or the VM itself.
- Successfully tested with Windows 10, 11, Server 2012 R2, 2016, 2019, 2022, and 2025 using the
windows_secureboot mode. - Older Windows versions (7, 8) may have issues, potentially related to ISO download or compatibility.
- TPM (Trusted Platform Module) emulation does not seem fully functional, but Secure Boot works. Windows 11 and Server 2022/2025 install correctly despite the lack of a fully functional TPM device in QEMU.
- This setup builds upon the work started by Husky110 in the kasm-qemu-docker repository.
- It incorporates the core functionality of the excellent dockur/windows project.
Windows inside a Docker container.
- ISO downloader
- KVM acceleration
- Web-based viewer
services:
windows:
image: dockurr/windows
container_name: windows
environment:
VERSION: "11"
devices:
- /dev/kvm
- /dev/net/tun
cap_add:
- NET_ADMIN
ports:
- 8006:8006
- 3389:3389/tcp
- 3389:3389/udp
volumes:
- ./windows:/storage
restart: always
stop_grace_period: 2mdocker run -it --rm --name windows -p 8006:8006 --device=/dev/kvm --device=/dev/net/tun --cap-add NET_ADMIN -v "${PWD:-.}/windows:/storage" --stop-timeout 120 dockurr/windowskubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/master/kubernetes.ymlVery simple! These are the steps:
-
Start the container and connect to port 8006 using your web browser.
-
Sit back and relax while the magic happens, the whole installation will be performed fully automatic.
-
Once you see the desktop, your Windows installation is ready for use.
Enjoy your brand new machine, and don't forget to star this repo!
By default, Windows 11 Pro will be installed. But you can add the VERSION environment variable to your compose file, in order to specify an alternative Windows version to be downloaded:
environment:
VERSION: "11"Select from the values below:
| Value | Version | Size |
|---|---|---|
11 |
Windows 11 Pro | 5.4 GB |
11l |
Windows 11 LTSC | 4.7 GB |
11e |
Windows 11 Enterprise | 4.0 GB |
10 |
Windows 10 Pro | 5.7 GB |
10l |
Windows 10 LTSC | 4.6 GB |
10e |
Windows 10 Enterprise | 5.2 GB |
8e |
Windows 8.1 Enterprise | 3.7 GB |
7u |
Windows 7 Ultimate | 3.1 GB |
vu |
Windows Vista Ultimate | 3.0 GB |
xp |
Windows XP Professional | 0.6 GB |
2k |
Windows 2000 Professional | 0.4 GB |
2025 |
Windows Server 2025 | 5.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 |
2003 |
Windows Server 2003 | 0.6 GB |
Tip
To install ARM64 versions of Windows use dockur/windows-arm.
To change the storage location, include the following bind mount in your compose file:
volumes:
- ./windows:/storageReplace the example path ./windows with the desired storage folder or named volume.
To expand the default size of 64 GB, add the DISK_SIZE setting to your compose file and set it to your preferred capacity:
environment:
DISK_SIZE: "256G"Tip
This can also be used to resize the existing disk to a larger capacity without any data loss.
Open 'File Explorer' and click on the 'Network' section, you will see a computer called host.lan.
Double-click it and it will show a folder called Data, which can be bound to any folder on your host via the compose file:
volumes:
- ./example:/dataThe example folder ./example will be available as \\host.lan\Data.
Tip
You can map this path to a drive letter in Windows, for easier access.
By default, the container will be allowed to use a maximum of 2 CPU cores and 4 GB of RAM.
If you want to adjust this, you can specify the desired amount using the following environment variables:
environment:
RAM_SIZE: "8G"
CPU_CORES: "4"By default, a user called Docker is created during installation and its password is admin.
If you want to use different credentials, you can configure them in your compose file (only before installation):
environment:
USERNAME: "bill"
PASSWORD: "gates"By default, the English version of Windows will be downloaded.
But before installation you can add the LANGUAGE environment variable to your compose file, in order to specify an alternative language:
environment:
LANGUAGE: "French"You can choose between: ๐ฆ๐ช Arabic, ๐ง๐ฌ Bulgarian, ๐จ๐ณ Chinese, ๐ญ๐ท Croatian, ๐จ๐ฟ Czech, ๐ฉ๐ฐ Danish, ๐ณ๐ฑ Dutch, ๐ฌ๐ง English, ๐ช๐ช Estonian, ๐ซ๐ฎ Finnish, ๐ซ๐ท French, ๐ฉ๐ช German, ๐ฌ๐ท Greek, ๐ฎ๐ฑ Hebrew, ๐ญ๐บ Hungarian, ๐ฎ๐น Italian, ๐ฏ๐ต Japanese, ๐ฐ๐ท Korean, ๐ฑ๐ป Latvian, ๐ฑ๐น Lithuanian, ๐ณ๐ด Norwegian, ๐ต๐ฑ Polish, ๐ต๐น Portuguese, ๐ท๐ด Romanian, ๐ท๐บ Russian, ๐ท๐ธ Serbian, ๐ธ๐ฐ Slovak, ๐ธ๐ฎ Slovenian, ๐ช๐ธ Spanish, ๐ธ๐ช Swedish, ๐น๐ญ Thai, ๐น๐ท Turkish and ๐บ๐ฆ Ukrainian.
If you want to use a keyboard layout or locale that is not the default for your selected language, you can add KEYBOARD and REGION variables like this (before installation):
environment:
REGION: "en-US"
KEYBOARD: "en-US"Windows Server offers a minimalistic Core edition without a GUI. To select those non-standard editions, you can add a EDITION variable like this (before installation):
environment:
EDITION: "core"In order to download an unsupported ISO image, specify its URL in the VERSION environment variable:
environment:
VERSION: "https://example.com/win.iso"Alternatively, you can also skip the download and use a local file instead, by binding it in your compose file in this way:
volumes:
- ./example.iso:/boot.isoReplace the example path ./example.iso with the filename of your desired ISO file. The value of VERSION will be ignored in this case.
To run your own script after installation, you can create a file called install.bat and place it in a folder together with any additional files it needs (software to be installed for example).
Then bind that folder in your compose file like this:
volumes:
- ./example:/oemThe example folder ./example will be copied to C:\OEM and the containing install.bat will be executed during the last step of the automatic installation.
It's recommended to stick to the automatic installation, as it adjusts various settings to prevent common issues when running Windows inside a virtual environment.
However, if you insist on performing the installation manually at your own risk, add the following environment variable to your compose file:
environment:
MANUAL: "Y"The web-viewer is mainly meant to be used during installation, as its picture quality is low, and it has no audio or clipboard for example.
So for a better experience you can connect using any Microsoft Remote Desktop client to the IP of the container, using the username Docker and password admin.
There is a RDP client for Android available from the Play Store and one for iOS in the Apple Store. For Linux you can use FreeRDP and on Windows just type mstsc in the search box.
By default, the container uses bridge networking, which shares the IP address with the host.
If you want to assign an individual IP address to the container, you can create a macvlan network as follows:
docker network create -d macvlan \
--subnet=192.168.0.0/24 \
--gateway=192.168.0.1 \
--ip-range=192.168.0.100/28 \
-o parent=eth0 vlanBe sure to modify these values to match your local subnet.
Once you have created the network, change your compose file to look as follows:
services:
windows:
container_name: windows
..<snip>..
networks:
vlan:
ipv4_address: 192.168.0.100
networks:
vlan:
external: trueAn added benefit of this approach is that you won't have to perform any port mapping anymore, since all ports will be exposed by default.
Important
This IP address won't be accessible from the Docker host due to the design of macvlan, which doesn't permit communication between the two. If this is a concern, you need to create a second macvlan as a workaround.
After configuring the container for macvlan, it is possible for Windows to become part of your home network by requesting an IP from your router, just like a real PC.
To enable this mode, in which the container and Windows will have separate IP addresses, add the following lines to your compose file:
environment:
DHCP: "Y"
devices:
- /dev/vhost-net
device_cgroup_rules:
- 'c *:* rwm'To create additional disks, modify your compose file like this:
environment:
DISK2_SIZE: "32G"
DISK3_SIZE: "64G"
volumes:
- ./example2:/storage2
- ./example3:/storage3It is possible to pass-through disk devices or partitions directly by adding them to your compose file in this way:
devices:
- /dev/sdb:/disk1
- /dev/sdc1:/disk2Use /disk1 if you want it to become your main drive (which will be formatted during installation), and use /disk2 and higher to add them as secondary drives (which will stay untouched).
To pass-through a USB device, first lookup its vendor and product id via the lsusb command, then add them to your compose file like this:
environment:
ARGUMENTS: "-device usb-host,vendorid=0x1234,productid=0x1234"
devices:
- /dev/bus/usbIf the device is a USB disk drive, please wait until after the installation is fully completed before connecting it. Otherwise the installation may fail, as the order of the disks can get rearranged.
First check if your software is compatible using this chart:
| Product | Linux | Win11 | Win10 | macOS |
|---|---|---|---|---|
| Docker CLI | โ | โ | โ | โ |
| Docker Desktop | โ | โ | โ | โ |
| Podman CLI | โ | โ | โ | โ |
| Podman Desktop | โ | โ | โ | โ |
After that you can run the following commands in Linux to check your system:
sudo apt install cpu-checker
sudo kvm-okIf you receive an error from kvm-ok indicating that KVM cannot be used, please check whether:
-
the virtualization extensions (
Intel VT-xorAMD SVM) are enabled in your BIOS. -
you enabled "nested virtualization" if you are running the container inside a virtual machine.
-
you are not using a cloud provider, as most of them do not allow nested virtualization for their VPS's.
If you did not receive any error from kvm-ok but the container still complains about a missing KVM device, it could help to add privileged: true to your compose file (or sudo to your docker command) to rule out any permission issue.
You can use dockur/macos for that. It shares many of the same features, except for the automatic installation.
You can use qemus/qemu in that case.
Yes, this project contains only open-source code and does not distribute any copyrighted material. Any product keys found in the code are just generic placeholders provided by Microsoft for trial purposes. So under all applicable laws, this project will be considered legal.
The product names, logos, brands, and other trademarks referred to within this project are the property of their respective trademark holders. This project is not affiliated, sponsored, or endorsed by Microsoft Corporation.

