Зачем мне Windows на Linux-сервере
Для одного из проектов понадобился Word worker – отдельный сервис, который умеет открывать и обрабатывать .docx через настоящий Microsoft Word (нужен COM-движок, а не LibreOffice или python-docx – иначе вёрстка плывёт). А Word нормально живёт только на Windows.
У меня уже стоит и круглосуточно работает машина с Linux-сервером – на нём Docker, веб-сервисы, всякая мелочь. Арендовать ещё один VPS под Windows ради одной задачи – выглядело избыточно. Решил поднять Windows параллельно прямо на этой машине, как виртуалку.
Установка пакетов
Ubuntu/Debian. Ставим сразу всё, что понадобится позже – гипервизор, тулзу для создания VM, OVMF (UEFI-прошивка для виртуалок) и swtpm (эмулятор TPM):
1234sudo apt updatesudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients \ virtinst bridge-utils ovmf swtpm swtpm-toolssudo usermod -aG libvirt,kvm $USERПосле usermod – релогин (или newgrp libvirt), иначе virsh будет ругаться на права.
Готовим ISO
Нужны два ISO: сам Windows 11 и virtio-win.iso с драйверами для виртуального диска и сетевой карты. Без virtio-драйверов установщик Windows тупо не увидит наш диск на этапе выбора раздела.
virtio-win.iso берём с официального репозитория Fedora: fedorapeople.org/groups/virt/virtio-win. Windows-ISO – с любого источника, который тебе нравится.
Кладём оба файла в /var/lib/libvirt/images/ и переименовываем в короткие имена, чтобы потом команда не была километровой:
123456sudo mv ~/Downloads/ru-ru_windows_11_consumer_editions_*.iso \ /var/lib/libvirt/images/windows-11.isosudo mv ~/Downloads/virtio-win-*.iso /var/lib/libvirt/images/virtio-win.iso
sudo chown libvirt-qemu:kvm /var/lib/libvirt/images/*.isosudo chmod 644 /var/lib/libvirt/images/*.isoBridge-сеть и потерянный SSH
Чтобы виртуалка была видна в локалке (а не сидела за NAT'ом libvirt), нужен мост br0 поверх физического интерфейса. Конфиг netplan /etc/netplan/01-bridge.yaml:
1234567891011121314151617181920network: version: 2 renderer: networkd
ethernets: eno1: dhcp4: no
bridges: br0: interfaces: [eno1] addresses: [<HOST_IP>/24] routes: - to: default via: <GATEWAY_IP> nameservers: addresses: [1.1.1.1, 8.8.8.8] parameters: stp: false forward-delay: 0Я указал статический IP вместо DHCP – для сервера так удобнее, не плавает между перезагрузками. eno1 – это имя моей сетевой карты, у тебя оно может быть другим (ip a покажет, типа enp3s0 или ens18). <HOST_IP> и <GATEWAY_IP> – IP сервера и роутера в твоей LAN (например, 192.168.1.50/24 и 192.168.1.1). Применяем:
1sudo netplan trynetplan try я мгновенно потерял SSH. Нейронка обещала, что netplan try через 120 секунд сам откатит конфиг, если ты не подтвердил – на моей машине этого не произошло. Помогла только физическая перезагрузка сервера.Грабли №2: TPM 2.0 и Secure Boot
Первая попытка запустить установку – самая наивная (i440fx + BIOS):
123456virt-install --name windows10 --ram 8192 --vcpus 4 --cpu host \ --disk path=/var/lib/libvirt/images/windows-11.qcow2,size=80,bus=virtio \ --cdrom /var/lib/libvirt/images/windows-11.iso \ --disk path=/var/lib/libvirt/images/virtio-win.iso,device=cdrom \ --os-variant win10 --network bridge=br0,model=virtio \ --graphics vnc,listen=0.0.0.0 --noautoconsoleУстановщик загружается, но сразу выкидывает экран: «Компьютер должен поддерживать доверенный платформенный модуль 2.0. Компьютер должен поддерживать безопасную загрузку».
Windows 11 проверяет vTPM 2.0 и Secure Boot прямо на этапе установки. Без них дальше не пройти – это не обходится через какой-нибудь tweak в реестре установщика. Нужен честный UEFI-запуск с эмулятором TPM.
Грабли №3: No bootable option or device was found
Логика: переключусь на q35 (это уже UEFI-совместимый чипсет), добавлю --boot cdrom,hd,menu=on – и поедет:
1234567virt-install --name windows10 --ram 8192 --vcpus 4 --cpu host \ --machine q35 --boot cdrom,hd,menu=on \ --disk path=/var/lib/libvirt/images/windows-11.qcow2,size=80,bus=virtio \ --cdrom /var/lib/libvirt/images/windows-11.iso \ --disk path=/var/lib/libvirt/images/virtio-win.iso,device=cdrom \ --os-variant win11 --network bridge=br0,model=virtio \ --graphics vnc,listen=0.0.0.0 --noautoconsoleИ тут на VNC меня встречает грустный экран No bootable option or device was found и Boot Manager:

Причина в том, что --machine q35 сам по себе не включает UEFI – нужно явно указать OVMF-loader. Без него q35 пытается грузиться в legacy-BIOS-режиме, на котором эта связка работает криво, и установщик не находит загрузочное устройство. Плюс синтаксис --boot cdrom,hd,menu=on – устаревший, для UEFI нужен --boot loader=/usr/share/OVMF/OVMF_CODE_4M.secboot.fd,loader.readonly=yes.
Рабочая команда: UEFI + Secure Boot + vTPM 2.0
Финальный вариант, который успешно стартует и доходит до установщика Windows:
1234567891011121314151617sudo virt-install \ --name windows11 \ --ram 8192 \ --vcpus 4 \ --cpu host-passthrough \ --machine q35 \ --boot loader=/usr/share/OVMF/OVMF_CODE_4M.secboot.fd,loader.readonly=yes,loader.type=pflash,loader.secure=yes,nvram.template=/usr/share/OVMF/OVMF_VARS_4M.fd,bootmenu.enable=on \ --features smm.state=on \ --tpm backend.type=emulator,backend.version=2.0,model=tpm-crb \ --disk path=/var/lib/libvirt/images/windows11.qcow2,size=80,bus=virtio,format=qcow2 \ --cdrom /var/lib/libvirt/images/windows-11.iso \ --disk path=/var/lib/libvirt/images/virtio-win.iso,device=cdrom \ --os-variant win11 \ --network bridge=br0,model=virtio \ --graphics vnc,listen=0.0.0.0 \ --video qxl \ --noautoconsoleЧто тут важного:
OVMF_CODE_4M.secboot.fd+loader.secure=yes– UEFI-прошивка с включённым Secure Boot.smm.state=on– System Management Mode. Без него Secure Boot молча не работает: Windows-проверка вроде бы проходит, но реально ключи не сохраняются.nvram.template=OVMF_VARS_4M.fd– отдельный NVRAM на каждую VM (там Secure Boot хранит свои ключи).--tpm backend.type=emulator,backend.version=2.0,model=tpm-crb– даёт виртуальный TPM 2.0 через swtpm, именно его проверяет установщик.--cpu host-passthrough– пробрасываем все флаги CPU как есть. Win 11 проверяет конкретные инструкции – без passthrough может ругаться на «неподдерживаемый процессор».
Если у тебя нет файла OVMF_CODE_4M.secboot.fd – посмотри что есть командой ls /usr/share/OVMF/. На старых Ubuntu имена без _4M: OVMF_CODE.secboot.fd и OVMF_VARS.fd.
Boot Manager и virtio-драйверы
Стартуем VM и подключаемся к VNC:
12sudo virsh start windows11sudo virsh vncdisplay windows11vncdisplay покажет что-то вроде :0 – это значит порт 5900 (:1 → 5901, и т.д.).
На VNC видим Boot Manager (тот самый экран No bootable option). Это нормально – нужно вручную выбрать стрелками UEFI QEMU DVD-ROM QM00001 (это и есть наш windows-11.iso) и нажать Enter.
Когда выскочит надпись Press any key to boot from CD or DVD... – жми любую клавишу за 5 секунд, иначе свалишься обратно в Boot Manager.
Дальше идёт обычный установщик Windows. На шаге выбора диска список будет пустой – нужно нажать Load driver и подгрузить с virtio-win CD драйверы:
viostor– драйвер для virtio-диска (выбирай папку под нужную версию Win, обычноw11/amd64). Без него диск не виден.NetKVM– драйвер сетевой карты virtio. Без него после установки не будет сети.
Грабли №4: VM выключилась после первой перезагрузки и не вернулась
Драйверы подгружены, диск виден, тыкнул «Установить» – установщик проглотил файлы и радостно сообщил, что «компьютер сейчас будет перезагружен автоматически». Идёт обратный отсчёт, экран гаснет, VNC показывает чёрный квадрат. Жду минуту, две – никаких признаков жизни.
Иду в virsh list --all – статус shut off. То есть VM честно выключилась, но обратно сама не поднялась: libvirt по умолчанию не рестартует домен после ACPI shutdown изнутри гостя. Лечится в одну строчку:
1sudo virsh start windows11Подключаюсь к VNC заново – установка продолжается с того места, где остановилась. Чтобы такое не повторялось дальше (Win 11 ещё несколько раз перезагружается во время установки), можно сразу включить автозапуск VM при выключении гостя:
123456# автозапуск VM при старте хостаsudo virsh autostart windows11
# чтобы libvirt сам поднимал VM после внутренней перезагрузкиsudo virsh edit windows11# в <on_poweroff> и <on_reboot> поставь 'restart'Проверка XML конфигурации
Убедимся, что все нужные фичи реально включены:
1sudo virsh dumpxml windows11 | grep -E 'loader|smm|tpm|secure'Должно быть видно:
123456<feature enabled='yes' name='secure-boot'/><loader readonly='yes' secure='yes' type='pflash'>/usr/share/OVMF/OVMF_CODE_4M.secboot.fd</loader><smm state='on'/><tpm model='tpm-crb'> <alias name='tpm0'/></tpm>VNC через SSH-туннель
--graphics vnc,listen=0.0.0.0 я оставил для удобства первой установки, но в проде так оставлять нельзя – это незашифрованный VNC, открытый в LAN.
Правильно – туннелировать через SSH:
1ssh -L 5900:127.0.0.1:5900 user@serverИ тыкать VNC-клиентом в localhost:5900. После установки можно через virsh edit windows11 поменять listen='0.0.0.0' на listen='127.0.0.1' – тогда снаружи VNC-порт будет недоступен совсем.
Апдейт через неделю: тот же KVM, но через Docker
Через неделю после всей этой ручной настройки я наткнулся на статью на Хабре: «Запуск Windows-контейнеров под Linux и MacOS». Там показан почти тот же подход, только через образ dockurr/windows.
Важный нюанс: это не «Windows внутри обычного Docker-контейнера» в классическом смысле. Под капотом всё равно QEMU/KVM, просто Docker-контейнер берёт на себя скачивание ISO, установку, хранение диска, web-доступ и проброс RDP. То есть это тот же класс задач, но без ручного virt-install, OVMF, TPM и подгрузки virtio-драйверов в установщике.
Для моего сценария итоговый docker-compose.yml получился таким:
123456789101112131415161718192021services: windows: image: dockurr/windows container_name: windows environment: VERSION: "11" RAM_SIZE: "8G" CPU_CORES: "4" devices: - /dev/kvm - /dev/net/tun cap_add: - NET_ADMIN ports: - 3389:3389/tcp - 3389:3389/udp - 9031:5000 volumes: - ./windows:/storage restart: always stop_grace_period: 2mЧто тут важно:
/dev/kvm– без него это будет медленная эмуляция или контейнер вообще не стартанёт. На VPS почти всегда нужен dedicated server или включённая nested virtualization./dev/net/tun+NET_ADMIN– нужны для сетевой части VM../windows:/storage– постоянный диск Windows. Если снести volume/папку, потеряешь установленную систему.3389/tcpи3389/udp– нормальный RDP-доступ, сильно приятнее web/VNC.9031:5000– мой прикладной порт под Word worker внутри Windows.RAM_SIZEиCPU_CORES– сразу задают те же 8 ГБ и 4 ядра, которые я выделял руками черезvirt-install.
Если бы я нашёл это решение сразу, скорее всего начал бы с него. Ручной KVM/QEMU всё ещё полезен, когда нужен полный контроль над VM, сетью, UEFI, дисками и libvirt XML. Но если цель – просто поднять Windows рядом с Linux-сервисами и подключаться по RDP, Dockur закрывает задачу заметно быстрее.
Итоги
VM на 8 ГБ RAM и 4 ядрах живёт параллельно с Linux-сервером, не мешает Docker-контейнерам и остальным сервисам. Word worker крутится внутри Win, общается с основным приложением через HTTP по bridge-сети – для Linux-приложений виртуалка выглядит как обычная машина в LAN. А если не хочется руками собирать весь libvirt-пазл, можно начать с dockurr/windows: под капотом будет тот же KVM, но с гораздо более коротким входом.
- Для Win 11: обязательно UEFI (OVMF) + Secure Boot + vTPM 2.0.
--machine q35сам по себе UEFI не включает – нужен явныйloader=/usr/share/OVMF/OVMF_CODE_4M.secboot.fd.smm.state=on– обязательная пара к Secure Boot.- Без virtio-драйверов (
viostor,NetKVM) установщик не увидит диск. - Не правь сеть удалённо без out-of-band доступа –
netplan tryне всегда откатывается. - После
virt-installможет понадобитьсяvirsh startвручную. - Более быстрый путь –
dockurr/windowsчерез Docker Compose: нужен/dev/kvm, storage-volume и проброс RDP.
