├── .github └── workflows │ └── main.yml ├── .gitignore ├── Dockerfile ├── README.md ├── TU102.rom ├── build.sh ├── docker-compose.yml ├── docker-run.sh ├── init.sh ├── screenshot3.png ├── screenshot4.jpg └── start.sh /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | # 设置 docker 镜像名 11 | IMAGE_NAME: emu-windows 12 | 13 | jobs: 14 | 15 | build: 16 | 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Build the Docker image 22 | run: docker build . --file Dockerfile --tag $IMAGE_NAME 23 | - name: Log into registry 24 | run: echo "${{ secrets.ACCESS_TOKEN }}" | docker login -u emengweb --password-stdin 25 | 26 | - name: Push image 27 | run: | 28 | # 拼接镜像 id,这个镜像 id 就是在使用 docker 镜像时 pull 后面的名字。 29 | IMAGE_ID=emengweb/$IMAGE_NAME 30 | 31 | # 将所有的大写字母转为小写 32 | IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') 33 | 34 | # 从 GitHub.ref 中取出版本 35 | VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') 36 | 37 | # 从 tag 名字中替换 v 字符 38 | [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') 39 | 40 | # Use Docker `latest` tag convention 41 | [ "$VERSION" == "master" ] && VERSION=latest 42 | 43 | echo IMAGE_ID=$IMAGE_ID 44 | echo VERSION=$VERSION 45 | # 设置镜像 id 和版本号 46 | docker tag $IMAGE_NAME $IMAGE_ID:$VERSION 47 | # 进行 push 48 | docker push $IMAGE_ID:$VERSION 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | emugaming.qcow2 -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | RUN apk add qemu-system-x86_64 qemu-img ovmf socat 3 | COPY TU102.rom / 4 | COPY start.sh / 5 | COPY init.sh / 6 | RUN cp /usr/share/OVMF/OVMF_VARS.fd /qemu-win10.nvram 7 | CMD sh /init.sh $CPU $MEMERY $ISOFILE $USEKVM 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # emu-windows 2 | qemu for windows on docker. 3 | Run / Install / Whatever use for, it's UP On You. 4 | 5 | ### Request: 6 | ./win7.qcow2 - Disk volume 7 | 8 | ./iso - ISO Folder 9 | 10 | ./iso/virtio.iso - emu virtio 11 | 12 | ## Docker-compose 13 | Just Use ```docker-compose up -d``` 14 | 15 | **docker-compose.yml** 16 | ```bash 17 | version: '3.3' 18 | services: 19 | emu-windows: 20 | image: emengweb/emu-windows:latest 21 | privileged: false 22 | environment: 23 | - CPU=2 # Default 1 24 | - MEMERY=3G # Default 1G 25 | #- ISOFILE=virtio.iso # Default Null, Can set ios for install custem OS 26 | #- USEKVM=true # if u run as VPS, disable it!!! 27 | volumes: 28 | - $PWD/win7.qcow2:/disk.qcow2 # Change Disk file name 29 | - $PWD/iso:/iso 30 | - /dev/bus/usb:/dev/bus/usb 31 | - /lib/modules:/lib/modules 32 | ports: 33 | - 6901:5900 # VNC no password 34 | - 9833:3389 # RDP Administrator password 35 | devices: 36 | #- /dev/kvm # if u run as VPS, disable it!!! 37 | - /dev/vfio/vfio 38 | #- /dev/vfio/1 39 | - /dev/bus/usb 40 | ulimits: 41 | memlock: 42 | soft: -1 43 | hard: -1 44 | ``` 45 | 46 | ## Docker-run 47 | 48 | ```bash 49 | docker run \ 50 | --volume $PWD/win7.qcow2:/disk.qcow2 `# the persistent volume` \ 51 | --volume $PWD/iso:/iso `# the iso folder` \ 52 | --interactive --tty \ 53 | -p 6901:5900 \ 54 | -p 9833:3389 \ 55 | -e CPU='2' \ 56 | -e MEMERY='3G' \ 57 | `# -e ISOFILE='virtio.iso'` \ 58 | `# -e USEKVM='true'` \ 59 | \ 60 | `# --device /dev/kvm # use hardware acceleration` \ 61 | --device /dev/vfio/vfio ` # vfio is used for PCIe passthrough` \ 62 | `# --device /dev/vfio/1 # the vfio IOMMU group` \ 63 | --ulimit memlock=-1:-1 `# so DMA can happen for the vfio passthrough` \ 64 | --device /dev/bus/usb `# since we use usb-host device passthrough (note you can specify specific devices too)` \ 65 | --volume /dev/bus/usb:/dev/bus/usb `# to allow for hot-plugging of USB devices` \ 66 | --volume /lib/modules:/lib/modules `# needed for loading vfio` \ 67 | --privileged `# needed for allowing hot-plugging of USB devices, but should be able to replace with cgroup stuff? also needed for modprobe commands` \ 68 | emengweb/emu-windows:latest 69 | ``` 70 | 71 | ## Building Dockerfile 72 | ```bash 73 | DOCKER_BUILDKIT=1 docker build -t emu-windows . 74 | ``` 75 | 76 | ## Create virtual disk 77 | ```bash 78 | qemu-img create -f qcow2 disk.qcow2 20G 79 | ``` 80 | 81 | 82 | 83 | 84 | # Inspire by lg/emugaming, many thanks! 85 | 86 | Given a QEMU Windows 10 image (you can create one as you normally would, name it `emugaming.qcow2`), this will run it on Docker with GPU/vfio passthrough. Works great for near-native gaming. 87 | 88 | Runs on Linux and with some changes should also be able to run on Windows or MacOS hosts (I think MacOS supports VT-d?). Note that you'll need a ROM dump of your GPU if you're passing through the primary boot video card. Also you'll need the kernel parameters `iommu=pt intel_iommu=on` to enable IOMMU/VT-d. 89 | 90 | Still working on making stuff actually configurable, but make your modifications in `start.sh` for configuring your components. Ideally in the future you'll be able to just run `docker run lg/emugaming` with hardly any parameters and all will be automatic, including generating the Windows 10 image from MS. 91 | 92 | ## Building 93 | 94 | ```bash 95 | DOCKER_BUILDKIT=1 docker build -t emugaming . 96 | ``` 97 | 98 | ## Creating Windows 10 image 99 | 100 | 1. Create a disk image using `qemu-img create -f qcow2 emugaming.qcow2 100G` on the host. You might need the `qemu-img` package. 101 | 2. Run the `docker run` command below but appending `ash` at the end to enter the docker image. This will open ash, Alpine's bash equivalent. 102 | 3. Download the [latest Windows 10 ISO](https://www.microsoft.com/en-us/software-download/windows10ISO) from Microsoft. 103 | 4. Download the [latest stable virtio-win driver pack](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso) from Fedora or [their github](https://github.com/virtio-win/kvm-guest-drivers-windows). 104 | 5. Run a `qemu` command similar to `start.sh`, except: 105 | - I'd recommend not using the GPU passthrough just yet. Remove the `vfio-pci`, `usb-host`, `vga` and `nographic` lines, and instead pass through `-vnc 0.0.0.0:0 -vga qxl -device usb-tablet` and using VNC to install Windows and getting the image stable. 106 | - Note you need to mount the two ISOs you downloaded `-cdrom win10.iso -cdrom virtio.iso` 107 | - When you install Windows it won't see your hard drive, click Have Disk and select the `viostor` driver in the VirtIO driver ISO 108 | - You can use the `quit` or `system_powerdown` commands in qemu to do a hard shutdown or a soft one 109 | 6. Once Windows is installed, I'd recommend some things to change: 110 | - Disable hibernation and standby (its ok to keep monitor standby on though) 111 | - Set `bcdedit /set '{current}' recoveryenabled No` since it can get confusing when debugging video card passthrough problems 112 | - Install the remainder of the Virtio drivers, plus other missing drivers too (like video card) 113 | 114 | ## Running 115 | 116 | ```bash 117 | docker run \ 118 | --volume $PWD/emugaming.qcow2:/emugaming.qcow2 `# the persistent volume` \ 119 | --interactive --tty \ 120 | \ 121 | --device /dev/kvm `# use hardware acceleration` \ 122 | --device /dev/vfio/vfio ` # vfio is used for PCIe passthrough` \ 123 | --device /dev/vfio/1 `# the vfio IOMMU group` \ 124 | --ulimit memlock=-1:-1 `# so DMA can happen for the vfio passthrough` \ 125 | --device /dev/bus/usb `# since we use usb-host device passthrough (note you can specify specific devices too)` \ 126 | --volume /dev/bus/usb:/dev/bus/usb `# to allow for hot-plugging of USB devices` \ 127 | --volume /lib/modules:/lib/modules `# needed for loading vfio` \ 128 | --privileged `# needed for allowing hot-plugging of USB devices, but should be able to replace with cgroup stuff? also needed for modprobe commands` \ 129 | emugaming 130 | ``` 131 | 132 | To use the QEMU Monitor command-line: 133 | ```bash 134 | docker exec -it socat - UNIX-CONNECT:/var/run/qemu_monitor 135 | ``` 136 | 137 | To start this docker image at boot: 138 | ```bash 139 | docker run \ 140 | ... 141 | --detach \ 142 | --restart unless-stopped \ 143 | ... 144 | ``` 145 | 146 | ## TODO 147 | 148 | - Make GPU params configurable (like address of devices and ROM file) 149 | - Make USB devices passed-through configurable 150 | - Make CPU cores configurable (or autodetect max?) 151 | - Detect and complain when IOMMU isnt on or devices not passthrough-able 152 | - Generate Windows 10 image automatically based on official Microsoft images 153 | - Remove the need for `--privileged` and broad permissions. Should be possible... 154 | - Remove the sleep statement in `start.sh` 155 | - Fix windows launching recover mode 156 | - Get this built for k3s or another Kubernetes distro 157 | 158 | ## Screenshots 159 | 160 | ### GPU and CPU passed-through 161 | ![GPU and CPU passed-through](screenshot3.png) 162 | 163 | ### Overwatch at 3840x2160 on maxed Epic settings 164 | ![Overwatch at 3840x2160 on maxed Epic settings](screenshot4.jpg) 165 | -------------------------------------------------------------------------------- /TU102.rom: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emengweb/emu-windows/7ba22f9268d9e3c0bbf7f8f3d0d09062a377fb79/TU102.rom -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | DOCKER_BUILDKIT=1 docker build -t emengweb/emu-windows . 2 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | services: 3 | emu-windows: 4 | image: emengweb/emu-windows:latest 5 | privileged: false 6 | environment: 7 | - CPU=2 # Default 1 8 | - MEMERY=3G # Default 1G 9 | #- ISOFILE=virtio.iso # Default Null, Can set ios for install custem OS 10 | #- USEKVM=true # if u run as VPS, disable it!!! 11 | volumes: 12 | - $PWD/win7.qcow2:/disk.qcow2 # Change Disk file name 13 | - $PWD/iso:/iso 14 | - /dev/bus/usb:/dev/bus/usb 15 | - /lib/modules:/lib/modules 16 | ports: 17 | - 6901:5900 # VNC no password 18 | - 9833:3389 # RDP Administrator password 19 | devices: 20 | #- /dev/kvm # if u run as VPS, disable it!!! 21 | - /dev/vfio/vfio 22 | #- /dev/vfio/1 23 | - /dev/bus/usb 24 | ulimits: 25 | memlock: 26 | soft: -1 27 | hard: -1 28 | -------------------------------------------------------------------------------- /docker-run.sh: -------------------------------------------------------------------------------- 1 | docker run \ 2 | --volume $PWD/win7.qcow2:/disk.qcow2 `# the persistent volume` \ 3 | --volume $PWD/iso:/iso `# the iso folder` \ 4 | --interactive --tty \ 5 | -p 6901:5900 \ 6 | -p 9833:3389 \ 7 | -e CPU='2' \ 8 | -e MEMERY='3G' \ 9 | `# -e ISOFILE='virtio.iso'` \ 10 | `# -e USEKVM='true'` \ 11 | \ 12 | `# --device /dev/kvm # use hardware acceleration` \ 13 | --device /dev/vfio/vfio ` # vfio is used for PCIe passthrough` \ 14 | `# --device /dev/vfio/1 # the vfio IOMMU group` \ 15 | --ulimit memlock=-1:-1 `# so DMA can happen for the vfio passthrough` \ 16 | --device /dev/bus/usb `# since we use usb-host device passthrough (note you can specify specific devices too)` \ 17 | --volume /dev/bus/usb:/dev/bus/usb `# to allow for hot-plugging of USB devices` \ 18 | --volume /lib/modules:/lib/modules `# needed for loading vfio` \ 19 | --privileged `# needed for allowing hot-plugging of USB devices, but should be able to replace with cgroup stuff? also needed for modprobe commands` \ 20 | emu-windows 21 | 22 | # ash 23 | 24 | # --detach \ 25 | # --restart unless-stopped \ 26 | # -e USEKVM='true' \ 27 | -------------------------------------------------------------------------------- /init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/ash 2 | 3 | CPU=$1 4 | MEMERY=$2 5 | ISOFILE=$3 6 | USEKVM=$4 7 | 8 | # disconnect all virtual terminals (for GPU passthrough to work) 9 | test -e /sys/class/vtconsole/vtcon0/bind && echo 0 > /sys/class/vtconsole/vtcon0/bind 10 | test -e /sys/class/vtconsole/vtcon1/bind && echo 0 > /sys/class/vtconsole/vtcon1/bind 11 | test -e /sys/devices/platform/efi-framebuffer.0/driver && echo "efi-framebuffer.0" > /sys/devices/platform/efi-framebuffer.0/driver/unbind 12 | 13 | 14 | # run qemu 15 | #qemu-system-x86_64 -smp 2 -m 4G -drive if=none,id=disk0,cache=none,format=qcow2,aio=threads,file=/disk.qcow2 -vnc 0.0.0.0:0 -cdrom /iso/win7.iso & 16 | 17 | # disk visible 18 | #qemu-system-x86_64 -smp 3,cores=3,threads=1,sockets=1 -m 3G -net user -net nic,model=e1000 -soundhw all -usb -device usb-tablet -usb -device usb-mouse -hda /disk.qcow2 -vnc 0.0.0.0:0 -cdrom /iso/virtio.iso & 19 | 20 | 21 | if [ -n "$1" ] 22 | then 23 | CPU=$1 24 | else 25 | CPU=1 26 | fi 27 | 28 | if [ -n "$2" ] 29 | then 30 | MEMERY=$2 31 | else 32 | MEMERY=1G 33 | fi 34 | 35 | if [ -n "$3" ] 36 | then 37 | ISOFILE="-cdrom /iso/$3" 38 | else 39 | ISOFILE= 40 | fi 41 | 42 | if [ -n "$4" ] 43 | then 44 | USEKVM='-enable-kvm' 45 | else 46 | USEKVM= 47 | fi 48 | 49 | # open RDP Port for Docker 50 | # -net user,hostfwd=tcp::3389-:3389 51 | qemu-system-x86_64 -monitor stdio -smp $CPU,cores=$CPU,threads=1,sockets=1 -m $MEMERY -net user,hostfwd=tcp::3389-:3389 -net nic,model=e1000 -soundhw all -usb -device usb-tablet -usb -device usb-mouse -hda /disk.qcow2 -vnc 0.0.0.0:0 $ISOFILE $USEKVM & 52 | QEMU_PID=$! 53 | 54 | while [ -e /proc/$QEMU_PID ]; do sleep 1; done 55 | -------------------------------------------------------------------------------- /screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emengweb/emu-windows/7ba22f9268d9e3c0bbf7f8f3d0d09062a377fb79/screenshot3.png -------------------------------------------------------------------------------- /screenshot4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emengweb/emu-windows/7ba22f9268d9e3c0bbf7f8f3d0d09062a377fb79/screenshot4.jpg -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/ash 2 | 3 | # disconnect all virtual terminals (for GPU passthrough to work) 4 | test -e /sys/class/vtconsole/vtcon0/bind && echo 0 > /sys/class/vtconsole/vtcon0/bind 5 | test -e /sys/class/vtconsole/vtcon1/bind && echo 0 > /sys/class/vtconsole/vtcon1/bind 6 | test -e /sys/devices/platform/efi-framebuffer.0/driver && echo "efi-framebuffer.0" > /sys/devices/platform/efi-framebuffer.0/driver/unbind 7 | 8 | # load vfio drivers onto devices if it's not loaded (for GPU passthrough to work) 9 | modprobe vfio_pci 10 | modprobe vfio_iommu_type1 11 | for pci_id in "0000:01:00.0" "0000:01:00.1" "0000:01:00.2" "0000:01:00.3"; do 12 | test -e /sys/bus/pci/devices/$pci_id/driver && echo -n "$pci_id" > /sys/bus/pci/devices/$pci_id/driver/unbind 13 | echo "$(cat /sys/bus/pci/devices/$pci_id/vendor) $(cat /sys/bus/pci/devices/$pci_id/device)" > /sys/bus/pci/drivers/vfio-pci/new_id 14 | done 15 | while [ ! -e /dev/vfio ]; do sleep 1; done 16 | 17 | # gracefully shut down QEMU when docker tries stopping it 18 | trap 'echo system_powerdown | socat - UNIX-CONNECT:/var/run/qemu_monitor' SIGTERM 19 | 20 | # run qemu 21 | qemu-system-x86_64 \ 22 | -nodefaults \ 23 | -monitor stdio \ 24 | -monitor unix:/var/run/qemu_monitor,server,nowait `# so we can send system_powerdown instead of hard stop when docker shuts down` \ 25 | \ 26 | -machine type=q35 `# allows for PCIe` \ 27 | -drive if=pflash,format=raw,readonly,file=/usr/share/OVMF/OVMF_CODE.fd `# read-only UEFI bios` \ 28 | -drive if=pflash,format=raw,file=/qemu-win10.nvram `# UEFI writeable NVRAM` \ 29 | -rtc clock=host,base=localtime `# faster boot aparently` \ 30 | -device qemu-xhci `# USB3 bus` \ 31 | \ 32 | -enable-kvm \ 33 | -cpu host,check,enforce,hv_relaxed,hv_spinlocks=0x1fff,hv_vapic,hv_time,l3-cache=on,-hypervisor,kvm=off,migratable=no,+invtsc,hv_vendor_id=1234567890ab \ 34 | -smp cpus=20,cores=10,threads=2,sockets=1 \ 35 | -m 12G \ 36 | \ 37 | -object iothread,id=io1 \ 38 | -device virtio-blk-pci,drive=disk0,iothread=io1 \ 39 | -drive if=none,id=disk0,cache=none,format=qcow2,aio=threads,file=/emugaming.qcow2 \ 40 | \ 41 | -nic user,model=virtio-net-pci `# simple passthrough networking that cant ping` \ 42 | \ 43 | -device vfio-pci,host=01:00.0,multifunction=on,x-vga=on,rombar=1,romfile=/TU102.rom \ 44 | -device vfio-pci,host=01:00.1 `# audio` \ 45 | \ 46 | -device usb-host,vendorid=0x1532,productid=0x0062 `# razer atheris mouse` \ 47 | -device usb-host,vendorid=0x05ac,productid=0x0267 `# apple magic keyboard` \ 48 | \ 49 | -vga none \ 50 | -nographic & 51 | QEMU_PID=$! 52 | 53 | while [ -e /proc/$QEMU_PID ]; do sleep 1; done 54 | --------------------------------------------------------------------------------