This guide documents the architecture, internals, configuration surface, and operational concerns of dockur/windows for administrators, DevOps engineers, and developers who deploy or extend it. It is the companion to the User Guide, which covers installation and everyday use.
dockur/windows is a thin, Windows-specific layer on top of the qemux/qemu base image. It contributes the logic to acquire Windows installation media, inject virtio drivers and an unattended-setup answer file, rebuild a bootable ISO, and share files back to the host. The heavy lifting of disk creation, networking, display, and the QEMU command line lives in the base image.
1. Executive Summary
| Aspect | Summary |
|---|---|
| What it is | A Docker image that runs Windows in a QEMU/KVM virtual machine, with fully unattended installation |
| Base image | qemux/qemu:7.32 (provides the VM runtime, disk/network/display, web viewer) |
| This repo's role | Windows media download + driver injection + unattended answer file + ISO rebuild + Samba share + graceful shutdown |
| Language | Bash (six scripts in src/), plus per-version unattend XML assets |
| Entry point | /usr/bin/tini -s /run/entry.sh |
| Acceleration | Requires host KVM (/dev/kvm); x64 only |
| Exposed ports | 8006 (web viewer / noVNC), 3389 (RDP, TCP+UDP) |
| Persistent state | /storage volume (virtual disk + marker files) |
| Licensing | MIT-style open source; no Microsoft media redistributed (downloaded at runtime) |
Note
This is an x64 image. The ARM64 variant is a separate image,
dockurr/windows-arm, referenced as a multi-arch build target in the Dockerfile (FROM dockurr/windows-arm:${VERSION_ARG} AS build-arm64).
2. Architecture
2.1 High-level layout
Host (Linux with KVM)
┌───────────────────────────────────────────────┐
│ Docker / Podman / Kubernetes │
│ │
│ ┌────────────── Container ──────────────┐ │
│ │ tini → /run/entry.sh │ │
│ │ │ │ │
│ │ ├─ source base-image scripts ────────┼──┼─ from qemux/qemu:7.32
│ │ │ (start, utils, reset, server, │ │ (disk, network, display,
│ │ │ disk, display, network, boot, │ │ web viewer, QEMU args)
│ │ │ proc, memory, balloon, config, │ │
│ │ │ finish) │ │
│ │ │ │ │
│ │ ├─ source THIS repo's scripts ───────┼──┼─ src/*.sh
│ │ │ define · mido · install · samba │ │
│ │ │ power │ │
│ │ │ │ │
│ │ └─ exec qemu-system-x86_64 ──────────┼──┼─ the Windows VM
│ │ ├─ /dev/kvm (acceleration) │ │
│ │ ├─ virtio disk → /storage │ │
│ │ ├─ virtio-net → bridge/macvlan │ │
│ │ └─ VGA/PTY → noVNC :8006 │ │
│ └──────────────────────────────────────────┘ │
└───────────────────────────────────────────────┘
Browser :8006 (install/view) RDP client :3389 (daily use)
2.2 Boot orchestration (src/entry.sh)
entry.sh is the container entry point. It sets four defaults, then sources a fixed ordering of shell modules — most from the base image, the Windows-specific ones from this repo — before launching QEMU:
: "${APP:="Windows"}"
: "${PLATFORM:="x64"}"
: "${BOOT_MODE:="windows"}"
: "${SUPPORT:="https://github.com/dockur/windows"}"
. start.sh # Startup hook (base image)
. utils.sh # Load helper functions (base image)
. reset.sh # Initialize system (base image)
. server.sh # Start web server (base image)
. define.sh # Define versions (THIS repo)
. mido.sh # Download Windows (THIS repo)
. install.sh # Run installation (THIS repo)
. disk.sh # Initialize disks (base image)
. display.sh # Initialize graphics (base image)
. network.sh # Initialize network (base image)
. samba.sh # Configure Samba (THIS repo)
. boot.sh # Configure boot (base image)
. proc.sh # Initialize processor (base image)
. power.sh # Configure shutdown (THIS repo)
. memory.sh # Check available memory (base image)
. balloon.sh # Memory ballooning (base image)
. config.sh # Assemble QEMU arguments (base image)
. finish.sh # Finish initialization (base image)
Each sourced script runs at source time (they are procedural, not function libraries), so the install pipeline in install.sh executes as it is sourced — before QEMU launches. After all modules load:
cmd=(qemu-system-x86_64)
info "Booting ${APP}${BOOT_DESC} using QEMU v$version..."
if [[ "$SHUTDOWN" != [Yy1]* ]]; then
exec "${cmd[@]}" ${ARGS:+ $ARGS}
fi
When graceful shutdown is enabled (the default), QEMU is run in the background through a named pipe (qemu.pipe) so its serial output can be filtered with sed and tee'd to the PTY. A 30-second watchdog (boot) checks that the VM started, and on exit the finish handler (in power.sh) cleans up.
Important
Because base-image scripts are sourced by name with no path, the container's working directory is
cd /runand all scripts (this repo'ssrc/plus the base image's) are copied to/run/. This is set in the Dockerfile:COPY --chmod=755 ./src /run/.
2.3 The Windows-specific modules
| Module | Responsibility |
|---|---|
define.sh |
Normalizes the VERSION and LANGUAGE inputs; holds the version-ID tables, the language/culture map, edition naming, and the mirror URL/hash/size tables; sets per-version QEMU machine quirks (setMachine) |
mido.sh |
Acquires media: the Microsoft consumer/eval download APIs, the ESD catalog path, and the four numbered fallback mirrors; verifies checksums |
install.sh |
The install pipeline: detect custom ISO, download, extract, detect version/language, inject drivers + answer file, rebuild bootable ISO, persist settings |
samba.sh |
Configures and starts Samba (and wsdd/NetBIOS) to expose the host share inside Windows |
power.sh |
Traps signals and performs a graceful ACPI shutdown of the guest; the finish cleanup handler |
entry.sh |
Sources everything and launches QEMU |
3. The Installation Pipeline (Core Internals)
The heart of this project is src/install.sh. Its driver section runs this sequence (each step exits with a distinct code on failure — see Section 7):
parseVersion normalize VERSION → canonical id (e.g. "11" → "win11x64")
parseLanguage normalize LANGUAGE → culture code
detectCustom look for a user-supplied /custom.iso or /boot.iso
startInstall decide whether install is needed; back up prior install
└─ if already installed → bootWindows() (reload saved settings) and run
downloadImage fetch ISO/ESD from MS or a mirror (mido.sh)
extractImage unpack ISO (7z) or apply ESD (wimlib) into a work dir
detectImage identify Windows version + language from install.wim
prepareImage per-version boot setup (etfsboot/efisys or legacy boot.img)
updateImage inject virtio drivers + the unattend XML answer file
removeImage delete the source ISO (unless custom)
buildImage rebuild a bootable ISO with genisoimage
finishInstall persist marker + settings files into /storage
3.1 Version normalization (define.sh)
parseVersion() maps a wide set of human aliases to a canonical internal ID. For example, all of 11, 11p, win11, pro11, windows 11 map to win11x64. Variant suffixes encode edition and channel:
| Suffix pattern | Meaning |
|---|---|
-enterprise-eval |
Enterprise Evaluation media |
-enterprise-iot-eval |
IoT Enterprise LTSC |
-enterprise-ltsc-eval |
LTSC channel |
-hv |
Hyper-V Server |
nano11 / core11 / tiny11 / tiny10 |
Community trimmed builds (with a DETECTED base for the answer file) |
A parallel getLanguage() table maps language inputs to a short code, display name, and BCP-47 culture (e.g. de-DE). parseLanguage() rejects anything not in the table (exit 56).
3.2 Media acquisition (mido.sh + define.sh)
The container tries multiple sources in order, controlled by downloadImage():
- Custom URL — if
VERSIONstarts withhttp, download it directly (two attempts with a retry delay). - Mido / Microsoft API (
isMido→getWindows):- Consumer Win11 (
download_windows): scrapesmicrosoft.com/.../software-download/windows11, obtains a product-edition ID, passes the Microsoft "session protection" handshake (vlscppe.microsoft.com/tags,ov-df.microsoft.com), then requests a SKU and a signed ISO link. - Evaluation / Server (
download_windows_eval): parses the Microsoft Evaluation Center page and follows thego.microsoft.com/fwlinkredirect.
- Consumer Win11 (
- ESD catalog (
isESD→getESD): for Win10/11 Pro and Enterprise, downloads the Microsoft ESD product catalog, filters by architecture/edition/language withxmllintXPath, and extracts the ESD URL, SHA-1, and size. - Fallback mirrors (
getLink1..getLink4,MIRRORS=4): static URL/hash/size tables pointing atdl.bobpony.com,files.dog/MSDN,nixsys.com/drivers, andarchive.org/download. Used when the Microsoft path is unavailable or for legacy versions.
verifyFile() checks the downloaded file's SHA-256 (or SHA-1 for ESD) against the table when VERIFY is enabled, and warns on size mismatch.
Note
The Microsoft download path deliberately mimics a real browser:
get_agent()computes an approximate current Firefox version from the system clock, and the curl calls pin--tlsv1.2 --http1.1with theAcceptheader removed. This is required to pass Microsoft's automated-download protection.
3.3 Image extraction (install.sh)
extractImage() branches on file type:
.esd→extractESD()useswimlib-imagexto apply the install image, export WinPE + Setup intoboot.wim, then export the selected edition intoinstall.wim(LZMS-compressed). A free-space pre-check requires ~9.6 GB..iso→7z xunpacks the ISO; the volume label is read withisoinfo. IfUNPACK=Y(set when the download was a compressed archive), a nested ISO is extracted first.
3.4 Version & platform detection (install.sh)
detectImage() reads install.wim/install.esd metadata (wimlib-imagex info -xml, converted from UTF-16LE):
checkPlatform()compares the image architecture (ARCH0=x86, 9=x64, 12=arm64) againstPLATFORM; a mismatch exits 67 ("You cannot boot ARM64 images on a x64 CPU").detectVersion()→selectVersion()mapsDISPLAYNAME/PRODUCTNAME/NAMEto an internal ID and prefers Enterprise → Ultimate → base edition that has a matchingassets/<id>.xmlanswer file.detectLanguage()reads the default language from the WIM XML.- The matching answer file is chosen by
setXML(), which searches/custom.xml,$STORAGE/custom.xml,/run/assets/custom.xml, the per-version asset, then a base fallback. If none is found, the install falls back to manual mode.
3.5 Driver and answer-file injection (install.sh)
updateImage() modifies the extracted media before the ISO is rebuilt:
addDrivers()extracts the bundled virtio-win driver archive (/var/drivers.txz, version pinned by the DockerfileVERSION_VIRTIOARG) and, viaaddDriver(), copies the per-OS driver set (qxl, viofs, NetKVM, viostor, vioscsi, Balloon, pvpanic, vioinput, viogpudo, vioserial, etc.) into$WinPEDriver$and the$OEM$tree usingwimlib-imagex update.- The selected
assets/<id>.xmlanswer file is copied in and patched byupdateXML()— resolution, UI/locale/keyboard culture, username, a base64-UTF16 encoded administrator password, server edition, and an optional productKEY. - For legacy Windows (2000/XP/2003),
prepareInstall()performs a very different flow: it editsTXTSETUP.SIFwithsedto register viostor/iaStor SCSI/SATA drivers, writes aWINNT.SIFunattend file, aninstall.reg, aninstall.vbs(renames the built-in Administrator and creates a desktop share shortcut), and acmdlines.txt. Generic, official Microsoft trial keys are embedded for XP/2003 only when no key is supplied.
3.6 ISO rebuild & persistence (install.sh)
buildImage() calls genisoimage with mode-specific flags:
- UEFI (non-legacy): El Torito + UDF, dual boot catalog (
etfsboot.com+efisys_noprompt.bin). - Legacy (2k/XP/2003, 9x, others): BIOS boot images with reduced ISO levels.
finishInstall() then writes a set of marker/settings files into /storage so the VM reboots with consistent hardware on subsequent runs. bootWindows() reads them back when an install already exists:
File in /storage |
Holds |
|---|---|
windows.boot |
Marker that installation completed |
windows.base |
Basename of the prepared ISO |
windows.ver |
Image build version (/etc/version) |
windows.mode |
BOOT_MODE (e.g. windows_legacy, windows_secure) |
windows.old |
Legacy QEMU MACHINE type |
windows.args |
Extra QEMU ARGS |
windows.vga / windows.usb / windows.net / windows.type |
Non-default VGA, USB controller, network adapter, disk type |
A magic first byte (0x16, or 0x17 for manual installs) is written into the ISO to mark it as already processed, so skipInstall() can short-circuit on later boots.
Important
Windows 11 unattended installs force
BOOT_MODE=windows_secure(Secure Boot + TPM) when required, and multi-socket systems are also switched to secure boot to work around a known freeze. These decisions are persisted inwindows.mode.
4. Configuration Reference
All configuration is via environment variables. Defaults marked (Dockerfile) are set as ENV in the image; the rest default inside the scripts.
4.1 Core variables
| Variable | Default | Source | Purpose |
|---|---|---|---|
VERSION |
11 |
Dockerfile | Windows edition or custom ISO URL |
RAM_SIZE |
4G |
Dockerfile | Guest memory (handled by base image) |
CPU_CORES |
2 |
Dockerfile | Guest vCPUs (handled by base image) |
DISK_SIZE |
64G |
Dockerfile | Main virtual disk size (base image) |
DISK2_SIZE / DISK3_SIZE |
(unset) | base image | Additional virtual disks |
USERNAME |
Docker |
install.sh |
Account created at install |
PASSWORD |
admin |
install.sh |
Password for that account |
LANGUAGE |
en |
define.sh |
Display language of downloaded edition |
REGION |
(culture) | install.sh |
User locale (UserLocale/SystemLocale) |
KEYBOARD |
(culture) | install.sh |
Input locale |
KEY |
(unset) | install.sh |
Product key injected into the answer file |
EDITION |
(unset) | define.sh |
Server edition (Standard/Datacenter/Core) |
4.2 Install behavior
| Variable | Default | Purpose |
|---|---|---|
MANUAL |
(unset) | Y skips the unattended answer file; manual Setup |
VERIFY |
(unset) | Y verifies the downloaded ISO checksum |
REMOVE |
(unset) | N keeps the installation ISO after setup |
WIDTH |
1280 |
Display width baked into legacy answer files |
HEIGHT |
720 |
Display height baked into legacy answer files |
ARGUMENTS |
(unset) | Raw extra QEMU arguments (escape hatch) |
4.3 Networking
| Variable | Default | Purpose |
|---|---|---|
DHCP |
(unset) | Y gives Windows its own router-assigned IP (needs /dev/vhost-net) |
ADAPTER |
(per version) | Guest NIC model; persisted in windows.net |
NETWORK |
(base image) | Networking backend (bridge, macvlan, passt, slirp) |
4.4 File sharing (Samba)
| Variable | Default | Purpose |
|---|---|---|
SAMBA |
Y |
Enable the Samba host share |
SAMBA_DEBUG |
N |
Stream Samba/wsdd logs |
SAMBA_INTERFACE |
(unset) | Extra interface to bind Samba to |
4.5 Shutdown
| Variable | Default | Purpose |
|---|---|---|
SHUTDOWN |
Y |
Enable graceful ACPI shutdown trapping |
TIMEOUT |
115 |
Seconds to wait for clean guest shutdown |
QEMU_TIMEOUT |
(unset) | Overrides TIMEOUT if set |
DEBUG |
(unset) | Verbose pipeline logging |
4.6 Volumes, devices, ports
| Mount / device | Role |
|---|---|
/storage |
Virtual disk + marker files (declared VOLUME in Dockerfile) |
/storage2, /storage3 |
Backing for DISK2_SIZE/DISK3_SIZE |
/shared, /shared2, /shared3 (or /data, /data2, /data3) |
Host folders exposed inside Windows as Data/Data2/Data3 |
/oem |
Copied to C:\OEM; install.bat runs at end of setup |
/boot.iso or /custom.iso |
Bind a local ISO to install instead of downloading |
/custom.xml |
Override the unattended answer file |
/disk1 (device) |
Pass-through main disk (formatted) |
/disk2+ (device) |
Pass-through secondary disks (untouched) |
/dev/kvm |
Required — KVM acceleration |
/dev/net/tun |
Required — container networking |
/dev/vhost-net |
Required only for DHCP=Y |
Port 8006/tcp |
Web viewer (noVNC) |
Port 3389/tcp+udp |
RDP |
Port 5900/tcp |
VNC (exposed in the Kubernetes manifest) |
5. Deployment
5.1 Docker Compose (recommended)
The canonical compose.yml is shown in the User Guide. Key production notes:
- Always bind
/storageto a host path or named volume. - Keep
stop_grace_period: 2mso the ACPI shutdown completes. restart: alwaysbrings the VM back after host reboots.
5.2 Kubernetes
The repo ships a complete kubernetes.yml with a PersistentVolumeClaim (64Gi), a Deployment, and a Service:
securityContext:
capabilities:
add:
- NET_ADMIN
privileged: true
/dev/kvm and /dev/net/tun are mounted from the host via hostPath: { type: CharDevice }, and the Service exposes ports 8006 (http), 3389 (rdp tcp+udp), and 5900 (vnc).
Caution
The Kubernetes manifest runs the pod
privileged: true. This is the simplest way to grant device access but gives the pod broad host privileges. In multi-tenant clusters, replace it with a tightly scoped device plugin and the minimum capabilities, and schedule only onto nodes with/dev/kvm.
5.3 Other launch paths
The README also documents GitHub Codespaces and the third-party WinBoat graphical installer as front ends for the same image.
6. Operations
6.1 Startup and viewing
docker compose up -d
docker compose logs -f # watch the install pipeline
Expected milestones in the log:
❯ Requesting Windows 11 Pro from the Microsoft servers...
❯ Downloading Windows 11 Pro...
❯ Extracting downloaded ISO image...
❯ Adding drivers to image...
❯ Adding autounattend.xml for automatic installation...
❯ Building Windows 11 Pro image...
❯ Booting Windows using QEMU v9.x.x...
❯ Windows started successfully, visit http://127.0.0.1:8006/ to view the screen...
6.2 Graceful shutdown (power.sh)
power.sh traps SIGTERM, SIGHUP, SIGABRT, and SIGQUIT. On signal, graceful_shutdown():
- Verifies the guest is past Setup (
ready()— checks the PTY for the Windows Boot Manager line, or legacy "Booting from Hard"). - Sends
system_powerdownover the QEMU monitor socket ($QEMU_DIR/monitor.sock) once per second. - Escalates to
SIGTERMnear the end of the timeout window, thenkill -9infinish()if still alive. finish()removes the post-install CD-ROM ISO (unlessREMOVE=N), kills helper daemons (Samba, wsdd, TPM, web, passt, dnsmasq, ballooning), and closes networking.
The timeout window is derived from TIMEOUT (minimum enforced; default 115 s), which is why stop_grace_period/--stop-timeout on the host must be at least as long.
Warning
If the host kills the container before the ACPI shutdown completes, Windows may mark the filesystem dirty and run
chkdsk(or lose unsaved writes) on next boot. Keep the host stop timeout ≥TIMEOUT.
6.3 Backups
/storage holds the entire machine. To back up, stop the container gracefully and copy the /storage folder. install.sh also auto-creates /storage/backups/<name> when it detects that VERSION or a custom ISO changed, preserving the previous windows.* data files before re-installing.
6.4 File sharing internals (samba.sh)
samba.sh writes /etc/samba/smb.conf with a guest-only, force user = root configuration (map to guest = Bad User, server min protocol = NT1 for legacy compatibility) and exposes the chosen share directory. It starts smbd, plus either nmbd (NetBIOS, for Windows 7 and older in legacy boot mode) or wsddn (Web Service Discovery, for Vista and newer). The share auto-selects /shared → $STORAGE/shared → /data → $STORAGE/data → a temp folder, and is reachable inside Windows at \\host.lan\Data.
Note
server min protocol = NT1(SMB1) is enabled to support legacy guests. SMB1 is deprecated and insecure on real networks — acceptable here because the share is on an internal container interface, not exposed to the host LAN. Do not bridge this Samba instance onto a production network.
7. Exit Codes
install.sh uses distinct exit codes, which are valuable for diagnosing failed installs from container logs:
| Code | Stage | Meaning |
|---|---|---|
| 50 | startInstall | Could not create the temp working directory |
| 56 | parseLanguage | Invalid LANGUAGE value |
| 58 | parseVersion | (version normalization failure) |
| 59 | detectCustom | Bind-mount maps to a non-existent file |
| 60 | abort / esd / unpack | Aborted to manual install, or ESD/unpack path ended |
| 61 | downloadImage | Could not download the ISO/ESD |
| 62 | extractImage | Could not extract the image |
| 63 | updateImage | Driver/answer-file injection failed |
| 64 | removeImage | Could not remove the source ISO |
| 65 | buildImage | genisoimage rebuild failed |
| 66 | prepareImage | Boot-image preparation failed |
| 67 | checkPlatform / custom.xml | Wrong CPU architecture, or /custom.xml bind missing |
| 68 | startInstall + bootWindows | Neither install nor existing-boot path succeeded |
| 69 | finishInstall | Could not persist install markers |
mido.sh additionally maps every curl exit status to a descriptive message via handle_curl_error() (DNS failure, TLS error, timeout, IP-based block, etc.).
8. Security Analysis
| Area | Finding |
|---|---|
| Privilege | Requires NET_ADMIN (and often privileged/device access). The container hosts a VM with raw device access — treat it as a privileged workload. |
| Default credentials | Docker / admin are well-known defaults. Always override USERNAME/PASSWORD, and never expose port 3389 publicly. |
| RDP exposure | Exposed RDP invites brute-force and account lockouts (community issue: locked accounts). Keep 3389 on a private network or behind a VPN. |
| Samba | Guest-only, force user = root, SMB1 enabled. Safe on the internal container interface; never reuse this config on a real LAN. |
| Product keys | Keys in define.sh/install.sh are official Microsoft generic trial/placeholder keys (no activation). The project distributes no copyrighted media. |
| Outbound network | The image makes outbound HTTPS calls to Microsoft and mirror hosts to fetch media. In locked-down environments, pre-stage a local ISO via /boot.iso and avoid the download path entirely. |
| Browser-like fetch | The Microsoft download handshake spoofs a Firefox user agent and replays Microsoft's anti-automation tokens. This is benign (it only retrieves the publicly available ISO) but will look anomalous to network monitoring. |
| macvlan isolation | With macvlan, the host cannot reach the container — a containment benefit, but it also means host-based monitoring won't see the guest's traffic. |
Important
The single most important hardening step is to change the default password and keep RDP off the public internet. The default
Docker/adminpair combined with an exposed3389is the most common real-world exposure for this image.
9. Build & Dependencies
9.1 Image build (Dockerfile)
FROM scratch AS build-amd64
COPY --from=qemux/qemu:7.32 / /
RUN apt-get install samba wimtools dos2unix cabextract libxml2-utils libarchive-tools
# + wsdd-native (VERSION_WSDD=1.24) installed from a .deb
COPY --chmod=755 ./src /run/
COPY --chmod=755 ./assets /run/assets
ADD https://github.com/qemus/virtiso-whql/releases/.../virtio-win-${VERSION_VIRTIO}.tar.xz /var/drivers.txz
VOLUME /storage
EXPOSE 3389 8006
ENV VERSION="11" RAM_SIZE="4G" CPU_CORES="2" DISK_SIZE="64G"
ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"]
| Build ARG / dependency | Pin | Role |
|---|---|---|
qemux/qemu |
7.32 |
Base VM runtime image |
VERSION_VIRTIO |
1.9.57 |
virtio-win WHQL driver bundle (/var/drivers.txz) |
VERSION_WSDD |
1.24 |
wsdd-native for Web Service Discovery |
samba |
apt | SMB host file sharing |
wimtools (wimlib-imagex) |
apt | WIM/ESD image manipulation |
cabextract |
apt | Extract Microsoft .cab catalogs |
libxml2-utils (xmllint) |
apt | XPath over the ESD product catalog |
libarchive-tools (bsdtar) |
apt | Extract the driver archive |
dos2unix (unix2dos) |
apt | CRLF conversion for answer/registry files |
The runtime tools 7z, genisoimage, isoinfo, wget, curl, jq, and nc come from the base image.
9.2 The assets/ directory
assets/*.xml holds per-version unattended answer files (win11x64.xml, win10x64-enterprise-eval.xml, …) — 40 templates spanning Vista through Server 2025. updateXML() patches these at runtime; detectImage()/setXML() select the right one.
10. Code Quality & Conventions
- Strict Bash everywhere: every script begins
set -Eeuo pipefail. Failures propagate, and the install pipeline maps each to an exit code. - Defensive defaults: variables use
: "${VAR:=""}"parameter expansion so the scripts run cleanly undernounset. - Procedural source modules: scripts are sourced (not exec'd) and run top-to-bottom at source time — order in
entry.shis significant. - Table-driven logic: version IDs, languages, editions, and the four mirror sets are all
casetables indefine.sh, making them easy to audit and extend. - Heavy reliance on external CLIs: the pipeline orchestrates
wimlib-imagex,genisoimage,7z,xmllint,sed,curl/wget. Behavior is sensitive to those tools' versions (pinned via the base image).
11. Potential Improvements
These are observations for maintainers and forkers, derived from the source and community issues. They are not defects in normal operation.
- [ ]
NET_RAWdiscoverability — RDP failing withoutNET_RAWon some hosts (community issue) is not surfaced in the default error path. A startup probe that detects the missing capability and prints a hint would cut support load. - [ ]
passtbackend stability — a reportedpasst.avx2SIGABRTcauses guest network loss on certain CPUs. Auto-falling back to bridge networking on a passt crash would harden the default path. - [ ] SMB1 default —
server min protocol = NT1is enabled unconditionally for legacy guests. Gating SMB1 behind a flag (or per-version) would reduce the attack surface for modern-only deployments. - [ ] Privileged Kubernetes manifest — shipping a least-privilege variant (device plugin + dropped
privileged: true) alongside the current manifest would suit hardened clusters. - [ ] Download-path observability — the Microsoft handshake has many failure points; a
DEBUG-independent structured log of which acquisition method (Mido/eval/ESD/mirror N) ultimately succeeded would aid air-gapped planning.
12. Quick Reference
# Start / stop
docker compose up -d
docker compose down # triggers graceful ACPI shutdown
# Logs & status
docker compose logs -f # watch install pipeline
docker exec windows cat /etc/version
# Common overrides (compose environment:)
VERSION: "10" # edition or ISO URL
RAM_SIZE: "8G"
CPU_CORES: "4"
DISK_SIZE: "256G"
USERNAME: "admin"; PASSWORD: "<strong>"
MANUAL: "Y" # manual Setup
DHCP: "Y" # own LAN IP (needs /dev/vhost-net)
VERIFY: "Y" # checksum the download
# Required runtime
--device=/dev/kvm --device=/dev/net/tun --cap-add NET_ADMIN
-p 8006:8006 -p 3389:3389/tcp -p 3389:3389/udp
-v ./windows:/storage
| Connect | Address | Notes |
|---|---|---|
| Web viewer | http://127.0.0.1:8006/ |
Install/quick view; low quality, no audio/clipboard |
| RDP | container IP :3389 |
Full quality; Docker/admin by default |