Discover an innovative and efficient method of deploying Windows OS (x64) on your linux system using the power of Vagrant VM, libvirt, and docker-compose. Together, these technologies help you containerize Windows OS, enabling you to manage a Windows instance just as you would any Docker container. This seamless integration into existing workflows significantly enhances convenience and optimizes resource allocation.
⭐ Don't forget to star the project if it helped you!
Ensure your system meets the following requirements:
-
Docker: Version 20 or higher (Install Docker)
-
Host OS: Linux
-
Virtualization Enabled:
- Check with:
lscpu | grep -i Virtualization
- Output indicates:
VT-x→ Intel virtualization is supported & enabled.AMD-V→ AMD virtualization is supported & enabled.
- If virtualization is not enabled, enable it in the BIOS/UEFI settings.
- Check with:
-
cgroup: hostin the compose file is required: libvirt and the daemons it spawns need full cgroup access, otherwise the container fails to start on cgroup v2 hosts.
- Create/Update the environmental file
.env
# Vagrant image settings
MEMORY=8000 # MiB (~8 GB)
CPU=4
DISK_SIZE=100 # GiB
- Create
docker-compose.yml
services:
win10:
image: docker.io/vaggeliskls/windows-in-docker-container:latest
platform: linux/amd64
env_file: .env
stdin_open: true
tty: true
privileged: true
cgroup: host
restart: always
ports:
- 3389:3389
- 2222:2222- Create
docker-compose.override.ymlwhen you want your VM to be persistent
services:
win10:
volumes:
- libvirt_data:/var/lib/libvirt
- vagrant_data:/root/.vagrant.d
- vagrant_project:/app/.vagrant
- libvirt_config:/etc/libvirt
volumes:
libvirt_data:
name: libvirt_data
vagrant_data:
name: vagrant_data
vagrant_project:
name: vagrant_project
libvirt_config:
name: libvirt_config- Run:
docker compose up -d
First boot takes several minutes — the Vagrant box is already baked into the image, but the VM still has to boot and run the provisioning script (Chocolatey install, disk resize, registry tweaks). Follow progress with
docker compose logs -f.
When you want to destroy everything
docker compose down -v
For debugging or testing, you can connect to the VM using Remote Desktop on port 3389.
| OS | Software |
|---|---|
| Linux | rdesktop → rdesktop <ip>:3389 or Remmina |
| MacOS | Microsoft Remote Desktop |
| Windows | Built-in Remote Desktop Connection |
You can connect via SSH using either the administrator or Vagrant user credentials.
ssh <user>@<host> -p 2222Default users based on the Vagrant image are:
- Administrator
- Username: Administrator
- Password: vagrant
- User
- Username: vagrant
- Password: vagrant
- Linux host only — depends on
/dev/kvmand libvirt; macOS and Windows hosts are not supported. - Eval license — the underlying box ships an evaluation copy of Windows Server 2022. Activation expires per Microsoft's eval terms.
- No synced folders —
rsync,smb, andnfsare all unwired in the Vagrantfile (rsync needs a Windows-side install before provisioning runs; SMB synced folders aren't supported with a Linux host; in-container NFS hitsno support in current kernel). - Performance — without nested KVM available to Docker (e.g. on a cloud VM that doesn't expose KVM), the guest falls back to plain QEMU and is several times slower.
KVM acceleration is not availablein logs → the host isn't exposing/dev/kvm. Check virtualization is enabled in BIOS, thekvmmodule is loaded (lsmod | grep kvm), and/dev/kvmexists on the host. The startup script falls back to QEMU automatically; expect a large slowdown.- Port 3389 / 2222 already in use → another RDP/SSH service is bound on the host. Stop it, or change the host-side port mapping in
docker-compose.yml. - Container exits immediately → almost always a cgroup or privilege problem. Confirm
privileged: trueandcgroup: hostare set, then checkdocker compose logs win10. vagrant uphangs at "Waiting for domain to get an IP address" → libvirt's default NAT network isn't running. Restart the container, or runvirsh net-start defaultfrom inside it.
