Self-hosted service
dockur/windows-arm avatar
dockur/windows-arm

dockur/windows-arm, and the Dockerfile that documents the whole install

Windows for ARM in a Docker container.

2,251 stars239 forksBatchfileMIT

At a glance

What is it?
This container downloads Windows installation media, unpacks the cabinet and image files, edits the registry offline and runs an unattended setup, then serves a desktop over a browser or RDP. Its Dockerfile is the clearest documentation: four pinned third-party versions as build arguments, a QEMU user-mode binary copied from a separate image, and an entry script taken from the sibling x64 project.
Who is it for?
Adopt dockur/windows-arm on an ARM Linux machine with KVM if you need a Windows environment you can rebuild from a compose file rather than a hand-installed virtual machine, and treat the four environment variables as the whole configuration surface rather than expecting a settings file. Do not try it on Docker Desktop on Linux, macOS or Windows 10, which the README rules out because KVM is not passed through to containers.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Batchfile, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the container actually does, step by step

The feature list is short, and the honest description is that this is an unattended Windows installer with a virtual machine attached.

It runs Windows inside a Docker container, for devices like the Raspberry Pi 5 and many others. It downloads and installs automatically, hands-free, with the README's own phrasing. It uses KVM acceleration, which is what makes the performance claim of near-native plausible on ARM hardware, and it lets you set CPU, memory and storage allocation, with dynamic memory allocation through a memory balloon. USB passthrough and host folder sharing are supported, as are four networking modes: NAT, user-mode, macvlan and macvtap.

What makes it a container rather than a virtual machine appliance is the packaging. The whole thing is a compose file, a Dockerfile and a `src` directory, and the README offers a Docker Compose snippet, a Docker CLI one-liner, a Kubernetes manifest, and even a GitHub Codespaces badge. It also names three desktop front ends, WinBoat, WinPodX and WinApps, that use this container as their backend. That is a real ecosystem around a small piece of infrastructure, and it tells you the container is considered the substrate rather than the product.

The compose file is the entire interface:

yaml
services:
  windows:
    image: dockurr/windows
    environment:
      VERSION: "11"
    devices:
      - /dev/kvm
      - /dev/net/tun
    cap_add:
      - NET_ADMIN
    ports:
      - 8006:8006
      - 3389:3389/tcp
    volumes:
      - ./windows:/storage
    stop_grace_period: 2m

Two lines in that file carry most of the meaning. `/dev/kvm` is the hardware virtualisation device, and without it the container falls back to emulation. `/dev/net/tun` plus `NET_ADMIN` is what lets the container create the userspace network interface the guest talks through. Both are privileges rather than configuration, which is why the requirements section starts with a host requirement rather than a software one.

The two published ports are equally deliberate. 8006 is the browser-based viewer, and the README says its main purpose is installation, since it is less responsive than RDP and does not support clipboard sharing. 3389 is Remote Desktop, published for both TCP and UDP in the full example.

The `stop_grace_period` of two minutes is the detail that shows the author has run this. A Windows installation being torn down mid-setup leaves a half-written image, and two minutes is the grace period that avoids it. The Docker CLI equivalent uses a stop timeout of 120 seconds for the same reason.

The Dockerfile as documentation: four pinned versions and a borrowed script directory

The Dockerfile in this repository is more informative than the README, and it is short enough to read in full.

The first stage is where the architecture becomes clear. It starts `FROM scratch` and then does an `ADD` of a Git URL, fetching the `dockur/windows` repository at master with a single file excluded. That is the x64 sibling project. So the ARM image is a delta: it takes the x64 project's `src` directory wholesale and then layers its own `src` over the top, because the second `COPY` of the local `./src/` runs after the one from the base. Any script change in the shared project reaches the ARM image at build time, and the ARM project only has to carry what actually differs.

The second stage copies the entire root filesystem from a separate image, `qemux/qemu-arm`, at a pinned tag. That is where the QEMU user-mode binary comes from, and pinning the tag means the QEMU version is a reviewable line in the Dockerfile rather than a floating dependency.

Then come the build arguments, and the pattern is the most transferable thing in this file:

bash
ARG VERSION_WSDD="1.27"
ARG VERSION_VIRTIO="0.1.285"
ARG VERSION_BLINTER="1.0.112"
ENV VERSION="11"
ENV RAM_SIZE="4G"

Every third-party tool the installation needs has its version as a build argument in one place: the Web Services Discovery daemon, the virtio Windows drivers, and the Blinter registry tool. Change a line, rebuild, and the diff shows exactly which dependency moved. That is better than a download URL with a version buried in it, and better than a floating tag.

The `TARGETARCH` build argument is present and used, and it appears in the name of the Web Services Discovery package that gets downloaded, which is what makes the build multi-architecture aware rather than nominally so.

The runtime configuration is four environment variables, and they are declared as `ENV` defaults in exactly the values the README documents: `VERSION` defaults to 11, `RAM_SIZE` to 4G, `CPU_CORES` to 2 and `DISK_SIZE` to 64G. There is no configuration file. If a setting exists, it is one of these four, and if it does not exist, it does not exist.

Two structural details close the file. `tini` is the entry point, invoked with the shell's exec mode, which is the correct way to run a container whose main process spawns children and needs to reap them. And the final `RUN` block removes the apt package lists and the temporary directories, which is the layer-hygiene practice that keeps a shipped image from carrying a download cache that every user pulls.

gcab, cabextract, wimtools and Blinter: the installation pipeline

The package list in the Dockerfile explains how an unattended Windows installation actually works, and it is a short list of very specific tools.

`gcab` is Microsoft's own cabinet extraction utility, and `cabextract` does the same job in open source. Both are there because Windows installation media ships as cabinet archives. `wimtools` handles the Windows Imaging Format, which is the container a Windows image actually lives in, and it is what turns downloaded media into something you can install from. `cabextract` and `wimtools` together are the extraction half of the pipeline.

`xmlstarlet` and `libxml2-utils` are for editing XML, and Windows answer files, the unattend mechanism that tells Setup what to install and how to configure, are XML. `xmlstarlet` is how the container modifies that file rather than shipping a fixed one. `icu-devtools` is an unexpected entry and is there for locale and text handling during image preparation. `mtools` manipulates FAT filesystem images directly, which is how the virtual disk is created and formatted without mounting it. `samba` is the host file sharing, which is why the README's `Shared` folder and `Z:` drive work through a Linux Samba share rather than a custom protocol.

Then there is Blinter, installed as a pinned Python package, and it is the piece that makes the difference between a bootable Windows image and a configured one. Blinter edits Windows registry hives offline, before the system has ever booted. That is how the container can set the machine's configuration, disable the things an unattended install would otherwise stall on, and apply settings that Setup would otherwise require a user to click through. Doing it offline is what makes the install genuinely hands-free rather than a sequence of synthetic keystrokes.

The virtio Windows drivers are fetched as a tarball from a release and placed at a fixed path, which is the mechanism behind the display driver option discussed later. The Web Services Discovery daemon is installed as a Debian package for the target architecture, and it is what lets a Windows network discovery client see machines on the host network.

Read together, the pipeline is: fetch official media, extract the cabinets, extract the image, edit the registry offline, apply an answer file, boot the installer, and only then attach the paravirtualised drivers. The last step is the one that explains the display behaviour in the README, because the guest has to reach the point of installing its own graphics driver before it can use an accelerated one.

Four environment variables, and the answers to the questions people actually ask

Because the configuration surface is four variables, most of what a user asks about reduces to a value for one of them, and the README's question-and-answer section is organised exactly that way.

`VERSION` selects which Windows edition is downloaded, defaulting to Windows 11 Pro, with the edition and download size listed in a table. `RAM_SIZE` and `CPU_CORES` control the guest's resources, defaulting to 4 GB and two cores, and both are set at creation time rather than resized live. `DISK_SIZE` sets the virtual disk, defaulting to 64 GB, and this one has a genuinely useful property: it can be applied to an existing disk to grow it without data loss. The catch is stated plainly, because Windows will not use space it has not been told about. After growing the disk the added capacity appears as unallocated, and you have to extend the partition yourself, with a link to Microsoft's own instructions.

Storage location is not one of the four variables, because it is a volume. The bind mount of a host directory to `/storage` is what determines where the disk image lives, and the README shows the snippet. The same mechanism configures file sharing, with a host directory mounted at `/shared` becoming a folder called `Shared` on the Windows desktop and a drive letter `Z:`. That is a genuinely useful default, since it means file exchange with a Windows guest needs no network share configuration and no credentials inside the guest.

Audio is the fifth setting, and it is the one with the most interesting behaviour. It is disabled by default unless you are using RDP, and setting it to `Y` streams audio to the browser rather than to a virtual sound device. It then has to be switched on under Settings, Advanced in the web viewer, and the stream is only active while that option is enabled, so it costs no bandwidth when idle. That is a careful design decision: audio in a browser viewer is expensive in latency and bandwidth, and making it opt-in at two levels rather than one is a reasonable interpretation of what users want.

The whole surface is therefore small enough to hold in your head, which is unusual for a virtualisation project and is the main reason this container is approachable. The cost of that design is that anything not in those four variables, the ports and the volumes, is not configurable, and the README is the only place that is documented.

ramfb on purpose, and a black screen that is a feature

The graphics setting is the most instructive configuration in the project, because the project chooses the slower option on purpose and says so.

By default the guest uses the `ramfb` display adapter, and the stated reason is that it does not require any additional graphics drivers. For better graphical performance you can set `VGA` to `virtio-gpu`, which uses a dedicated paravirtualised display driver and can provide better desktop responsiveness.

The catch is explained in a note, and it is the kind of explanation most projects omit. With `virtio-gpu`, the screen goes black during Windows setup until the stage where its driver is installed. Since Windows setup is exactly the phase where you are watching the screen to find out whether the installation is working, a black screen during that phase is indistinguishable from a failed one. So `ramfb` remains the default, and it is the default because it is the most reliable display output during early boot and installation.

This is a good example of choosing the option that optimises for the failure case rather than the success case. A user who leaves the default gets a working install and slightly sluggish desktop. A user who changes it gets a better desktop and has to accept a black screen during setup, and the README tells them exactly when it ends. The alternative, defaulting to the fast option and hoping people read the note, produces support questions from people who think their install hung.

The same reasoning appears elsewhere in the defaults. Audio is off. The web viewer is documented as being mainly for installation rather than for daily use. And the memory and CPU defaults of 4 GB and two cores are conservative enough that the container starts on a Raspberry Pi 5 with something else running, which is the device the project names first.

The documented password admin, and the two ports you should think about before publishing

The security posture of this project is defined by a small number of defaults, and they are all in the README rather than hidden.

The first is the credential. The README states that you can connect using any Microsoft Remote Desktop client to the IP of the container, using the username `Docker` and the password `admin`. That is a documented, fixed, guessable password, and it is the credential for a full Windows desktop. It exists because the installation has to be reachable before anyone has logged in to change it, which is a legitimate reason, and it is a real risk the moment the port is exposed anywhere but localhost.

The second is the port set. 8006 and 3389 are both published in the compose example. Port 3389 is Remote Desktop, and publishing it beyond your own network with the default password in place is the failure mode to avoid. Port 8006 is the browser viewer, and it is a full interactive console with no authentication layer of its own described in the README, which makes it the one to keep bound to localhost.

The third is the network mode flexibility. NAT, user-mode, macvlan and macvtap are all supported, and macvlan in particular puts the container directly on a physical network segment with its own address rather than behind NAT. That is the right choice when the guest needs to be discoverable on the network, which is presumably why it is offered, and it is also the choice that makes the default password reachable by anything else on that segment.

None of this is a criticism so much as a reading. The project's security model is the same as any virtual machine appliance: trust the network you put it on, and change the password. What makes it worth stating is that the README does not tell you to change the password, and the combination of a fixed default credential with two published ports is the specific thing an operator needs to notice.

The volume deserves one line of its own. A bind mount of a host directory to `/storage` means the entire Windows installation, including whatever ends up on its disk, is writable from the host as ordinary files. That is what makes backup, migration and destruction trivial, and it is also what makes a host compromise immediately a guest compromise, in both directions.

Docker Desktop is not supported, and the version table is not all Microsoft

Two things in the README will stop someone before they get started, and neither is obvious from the project's name.

The first is that Docker Desktop is not supported. The requirements say Docker or Podman on a Linux host with KVM support, or Docker Desktop or Podman Desktop on Windows 11 with nested virtualisation enabled. Then a note states that Docker Desktop on Linux, macOS and Windows 10 does not currently provide KVM access to containers and is therefore not supported. Since Docker Desktop is what a large share of people install on macOS and on Linux desktops, this rules out the most obvious way to try the project on a laptop. The workaround is a Linux host with a plain Docker or Podman installation and KVM passed through, or Windows 11 with nested virtualisation.

The second is the version table, and it deserves to be read carefully rather than skimmed. Six of the eight entries are Microsoft editions: Windows 11 Pro, Windows 11 LTSC, Windows 11 Enterprise, Windows 10 Pro, Windows 10 LTSC and Windows 10 Enterprise, with download sizes from 3.4 GB to 7.5 GB. The other two are `core11` and `tiny11`, labelled Tiny11 Core and Tiny11, at 3.0 GB and 5.1 GB.

Those two are not Microsoft downloads. Tiny11 is a third-party modified and debloated Windows image, and the fact that this project offers it as a selectable value is a deliberate choice to include a non-Microsoft artefact in the download list. It is worth knowing about for a second reason beyond provenance: a modified image changes the licensing position, because the terms that cover redistributing or altering a Windows image do not cover an image someone else has already altered.

The same licensing point applies to the six Microsoft entries, and more sharply to four of them. Windows 11 Pro and Windows 10 Pro can be activated with a retail licence you own. LTSC and Enterprise editions are licensed through volume licensing, so selecting `11l` or `10e` gives you an edition that a retail purchase does not cover. The container does not supply a licence and does not claim to, and Windows will run unactivated in a reduced mode until one is applied. That is a licensing and budgeting question to settle before you pick a value, not after.

The remaining row in the FAQ is the redirect for anyone on x64 hardware, which points at the `dockur/windows` project. That sibling is the same design on the other architecture, and given that the ARM image literally adds that repository at build time, they are maintained together in a way the naming does not make obvious.

Editorial conclusion

Adopt dockur/windows-arm on an ARM Linux machine with KVM if you need a Windows environment you can rebuild from a compose file rather than a hand-installed virtual machine, and treat the four environment variables as the whole configuration surface rather than expecting a settings file. Do not try it on Docker Desktop on Linux, macOS or Windows 10, which the README rules out because KVM is not passed through to containers. Verify first by starting the container with only the defaults and watching the web viewer on port 8006, then change the RDP password from the documented Docker/admin before you expose port 3389 anywhere but your own network.

Frequently asked questions

What is dockur/windows-arm?

It is a container image that runs Windows ARM64 inside Docker on devices such as the Raspberry Pi 5, with automatic download and hands-free installation, KVM acceleration, custom CPU, memory and storage allocation, memory ballooning, USB passthrough, host folder sharing, and NAT, user-mode, macvlan or macvtap networking. WinBoat, WinPodX and WinApps all use it as a backend.

What are the requirements for dockur/windows-arm?

Docker or Podman on a Linux host with KVM support, or Docker Desktop or Podman Desktop on Windows 11 with nested virtualisation enabled, plus at least 2 GB of RAM and 32 GB of free disk. The README notes that Docker Desktop on Linux, macOS and Windows 10 does not provide KVM access to containers and is not supported.

How do I choose which Windows version gets installed?

With the VERSION environment variable, defaulting to 11 for Windows 11 Pro. The listed values are 11, 11l, 11e, 10, 10l, 10e, core11 and tiny11, with download sizes from 3.0 GB to 7.5 GB. Two of those, core11 and tiny11, are third-party modified images rather than Microsoft editions.

How do I connect to the Windows desktop?

Start the container and open port 8006 in a browser, which is mainly intended for installation because it is less responsive than RDP and does not support clipboard sharing. For daily use, connect with any Microsoft Remote Desktop client to the container's IP on port 3389, using the username Docker and the password admin as documented.

How do I control CPU, RAM, disk size and audio?

RAM_SIZE defaults to 4G, CPU_CORES to 2 and DISK_SIZE to 64G, and the disk can be grown on an existing installation without data loss, after which you must extend the partition yourself because the space appears as unallocated. Audio is off unless using RDP; setting AUDIO to Y streams it to the browser once enabled under Settings, Advanced in the viewer.

How do I improve graphical performance in dockur/windows-arm?

Set VGA to virtio-gpu. The default is the ramfb adapter because it needs no additional graphics driver, and the README explains that with virtio-gpu the screen blacks out during Windows setup until the driver is installed, which is why ramfb remains the default for reliability during early boot.

Official sources

  1. dockur/windows-arm on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dockur-windows-arm.svg)](https://hysenlabs.com/projects/dockur-windows-arm)