# Windows in a Docker Container — User Guide

`dockur/windows` runs a **full Microsoft Windows operating system inside a Docker container**. It downloads the Windows installation media for you, performs a completely unattended installation, and gives you a running desktop you reach from your web browser or any Remote Desktop client — no manual setup, no product key hunting, no ISO wrangling.

This guide covers everything you need to install, configure, and operate a Windows container. It is written for end users running the published `dockurr/windows` image. For architecture, internals, security hardening, and the ISO download pipeline, see the companion **Admin Guide**.

> [!NOTE]
> This container runs **x64** versions of Windows. To run ARM64 Windows (for example on an Apple Silicon or Raspberry Pi host), use the sibling project [`dockur/windows-arm`](https://github.com/dockur/windows-arm) instead.

---

## 1. Quick Start

The fastest way to a running Windows 11 desktop:

=== "Docker Compose"

    Create a file named `compose.yml`:

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

    Then start it:

    ```bash
    docker compose up -d
    ```

=== "Docker CLI"

    ```bash
    docker run -it --rm --name windows \
      -e "VERSION=11" \
      -p 8006:8006 \
      --device=/dev/kvm \
      --device=/dev/net/tun \
      --cap-add NET_ADMIN \
      -v "${PWD:-.}/windows:/storage" \
      --stop-timeout 120 \
      docker.io/dockurr/windows
    ```

=== "Kubernetes"

    ```bash
    kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/master/kubernetes.yml
    ```

Once the container is running:

1. Open **[http://127.0.0.1:8006/](http://127.0.0.1:8006/)** in your web browser.
2. Watch the fully automatic installation proceed. Nothing is required from you.
3. When the Windows desktop appears, your machine is ready to use.

```text
❯ Booting Windows using QEMU v9.x.x...
❯ Downloading Windows 11 Pro...
❯ Windows started successfully, visit http://127.0.0.1:8006/ to view the screen...
```

> [!TIP]
> The default login created during installation is username `Docker` and password `admin`. You can change these — see [Setting the username and password](#43-set-the-username-and-password).

> [!IMPORTANT]
> Your host **must support KVM hardware acceleration** for the container to work. Most Linux hosts and Windows 11 (via WSL2) do; Windows 10 and macOS generally do not. Verify your system before troubleshooting anything else — see [Verifying KVM support](#101-verify-that-your-system-supports-kvm).

---

## 2. Requirements

| Requirement | Detail |
|---|---|
| **CPU virtualization** | Intel VT-x or AMD SVM, enabled in BIOS/UEFI |
| **KVM device** | `/dev/kvm` must be present and accessible on the host |
| **TUN device** | `/dev/net/tun` for container networking |
| **Capability** | `NET_ADMIN` added to the container |
| **Disk space** | Enough for the Windows image (see size table in [Section 3](#3-selecting-a-windows-version)) plus your data — default virtual disk is `64G` |
| **Container runtime** | Docker, Podman, or Kubernetes |

### Host compatibility chart

| Product | Linux | Windows 11 | Windows 10 | macOS |
|---|---|---|---|---|
| Docker CLI | ✅ | ✅ | ❌ | ❌ |
| Docker Desktop | ❌ | ✅ | ❌ | ❌ |
| Podman CLI | ✅ | ✅ | ❌ | ❌ |
| Podman Desktop | ✅ | ✅ | ❌ | ❌ |

> [!WARNING]
> Most **cloud providers do not permit nested virtualization** on standard VPS instances, so KVM is unavailable there. If you run the container inside another virtual machine, you must enable "nested virtualization" on that VM first.

---

## 3. Selecting a Windows Version

By default, **Windows 11 Pro** is installed. Set the `VERSION` environment variable to choose a different edition. The image is downloaded automatically on first run.

```yaml
environment:
  VERSION: "10"
```

| `VERSION` | Edition | Approx. download |
|---|---|---|
| `11` | Windows 11 Pro *(default)* | 7.9 GB |
| `11l` | Windows 11 LTSC | 4.7 GB |
| `11e` | Windows 11 Enterprise | 6.6 GB |
| `10` | Windows 10 Pro | 5.7 GB |
| `10l` | Windows 10 LTSC | 4.6 GB |
| `10e` | Windows 10 Enterprise | 5.2 GB |
| `8e` | Windows 8.1 Enterprise | 3.7 GB |
| `7u` | Windows 7 Ultimate | 3.1 GB |
| `vu` | Windows Vista Ultimate | 3.0 GB |
| `xp` | Windows XP Professional | 0.6 GB |
| `2k` | Windows 2000 Professional | 0.4 GB |
| `2025` | Windows Server 2025 | 7.6 GB |
| `2022` | Windows Server 2022 | 6.0 GB |
| `2019` | Windows Server 2019 | 5.3 GB |
| `2016` | Windows Server 2016 | 6.5 GB |
| `2012` | Windows Server 2012 | 4.3 GB |
| `2008` | Windows Server 2008 | 3.0 GB |
| `2003` | Windows Server 2003 | 0.6 GB |

> [!NOTE]
> The `VERSION` field is flexible about how you write it. For Windows 11 Pro, any of `11`, `win11`, `pro11`, `windows11`, or `windows 11` all resolve to the same image. The same shorthand applies across every version.

> [!TIP]
> The download sizes above are guidelines. Set [`DISK_SIZE`](#51-resize-the-main-disk) large enough to hold both the installed OS and your future data — the default `64G` is comfortable for most uses but small for heavy workloads.

---

## 4. Everyday Configuration

All configuration is done through environment variables in your `compose.yml` (or `-e` flags on the command line). The most common settings are below.

### 4.1 Set CPU and memory

By default Windows is allotted **2 CPU cores and 4 GB of RAM**. Adjust with:

```yaml
environment:
  RAM_SIZE: "8G"
  CPU_CORES: "4"
```

> [!CAUTION]
> Allocating most or all of your host's cores can cause the host (and laptops especially) to run hot during the install phase, which is CPU-intensive. Leave headroom for the host operating system.

### 4.2 Change where data is stored

The container keeps the virtual disk and all Windows data in the `/storage` volume. Bind it to a host folder so your machine survives container recreation:

```yaml
volumes:
  - ./windows:/storage
```

Replace `./windows` with any host folder or named volume.

> [!IMPORTANT]
> If you remove the `/storage` bind mount, your Windows installation lives only inside the container and is **lost when the container is deleted**. Always bind `/storage` for anything you want to keep.

### 4.3 Set the username and password

A user named `Docker` with password `admin` is created during installation. To use your own credentials:

```yaml
environment:
  USERNAME: "bill"
  PASSWORD: "gates"
```

> [!NOTE]
> These credentials are applied **during installation only**. Changing them after Windows is installed has no effect — change the password from inside Windows instead.

### 4.4 Choose the Windows display language

The English edition is downloaded by default. To download a localized edition, set `LANGUAGE` to a language name:

```yaml
environment:
  LANGUAGE: "French"
```

Supported languages: Arabic, Bulgarian, Chinese, Croatian, Czech, Danish, Dutch, English, Estonian, Finnish, French, German, Greek, Hebrew, Hungarian, Italian, Japanese, Korean, Latvian, Lithuanian, Norwegian, Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovenian, Spanish, Swedish, Thai, Turkish, and Ukrainian.

> [!WARNING]
> Localized editions are only available for the mainstream consumer versions (Windows 10/11 Pro). Older versions, LTSC/IoT, and most Server editions are **English-only**. Requesting another language for those will fail the download.

### 4.5 Set the keyboard layout and region

If you want a keyboard or regional locale that differs from the language default, set `KEYBOARD` and `REGION` explicitly (using BCP-47 culture codes such as `en-US`, `de-DE`, `fr-CA`):

```yaml
environment:
  LANGUAGE: "English"
  REGION: "en-US"
  KEYBOARD: "en-US"
```

### Configuration quick reference

| Variable | Default | Purpose |
|---|---|---|
| `VERSION` | `11` | Windows edition to install ([Section 3](#3-selecting-a-windows-version)) |
| `RAM_SIZE` | `4G` | Memory assigned to Windows |
| `CPU_CORES` | `2` | Virtual CPU cores assigned to Windows |
| `DISK_SIZE` | `64G` | Size of the main virtual disk |
| `USERNAME` | `Docker` | Account created during install |
| `PASSWORD` | `admin` | Password for that account |
| `LANGUAGE` | `English` | Display language of the downloaded edition |
| `REGION` | (language default) | Regional/locale culture code |
| `KEYBOARD` | (language default) | Keyboard input locale |
| `MANUAL` | (unset) | Set `Y` to skip unattended setup ([Section 7](#7-manual-installation)) |
| `DHCP` | (unset) | Set `Y` to give Windows its own router IP ([Section 8](#8-networking)) |
| `ARGUMENTS` | (unset) | Extra raw QEMU arguments (escape hatch) |

---

## 5. Working with Disks

### 5.1 Resize the main disk

To expand the default 64 GB disk, set `DISK_SIZE`:

```yaml
environment:
  DISK_SIZE: "256G"
```

This also works on an **existing** installation to grow the disk without data loss.

> [!IMPORTANT]
> After enlarging an existing disk, the extra space appears as **unallocated** inside Windows. You must extend the partition manually using Windows Disk Management to actually use it. Microsoft's guide: <https://learn.microsoft.com/en-us/windows-server/storage/disk-management/extend-a-basic-volume>.

### 5.2 Add extra disks

Attach additional virtual disks by declaring their size and a matching `/storageN` volume:

```yaml
environment:
  DISK2_SIZE: "32G"
  DISK3_SIZE: "64G"
volumes:
  - ./example2:/storage2
  - ./example3:/storage3
```

### 5.3 Pass through a physical disk

You can hand a real host disk or partition directly to Windows:

```yaml
devices:
  - /dev/sdb:/disk1
  - /dev/sdc1:/disk2
```

- `/disk1` becomes the **main drive** and **is formatted during installation**.
- `/disk2` and higher are added as **secondary drives** and are left untouched.

> [!CAUTION]
> The `/disk1` device is wiped and formatted by Windows Setup. Never map a disk containing data you care about to `/disk1`.

---

## 6. Sharing Files With the Host

After installation a folder named **`Shared`** appears on the Windows desktop. Use it to exchange files with the host machine. To point it at a specific host folder, bind that folder to `/shared`:

```yaml
volumes:
  - ./example:/shared
```

The contents of `./example` then appear in the `Shared` folder inside Windows.

> [!NOTE]
> The shared folder is provided over Samba (SMB) from the container. You can mount additional shares at `/shared2` and `/shared3` as well; they appear as `Data2` and `Data3`.

---

## 7. Manual Installation

The automatic installer applies many settings that prevent common problems when Windows runs in a virtual environment, so it is strongly recommended. If you nonetheless want to click through Windows Setup yourself:

```yaml
environment:
  MANUAL: "Y"
```

> [!WARNING]
> With `MANUAL: "Y"` you are responsible for partitioning, account creation, and driver selection. For Windows 11, the container automatically enables Secure Boot and a TPM so Setup's hardware checks pass — but you still drive the wizard manually through the [web viewer](#9-connecting-to-windows).

---

## 8. Networking

By default the container uses **bridge networking** and shares the host's IP address. Ports `8006` (web viewer) and `3389` (RDP) are published to the host. For most users this is all that's needed.

### 8.1 Give the container its own IP (macvlan)

To make the container appear as a separate device on your LAN, create a `macvlan` network:

```bash
docker network create -d macvlan \
    --subnet=192.168.0.0/24 \
    --gateway=192.168.0.1 \
    --ip-range=192.168.0.100/28 \
    -o parent=eth0 vlan
```

Adjust the addresses to match your network, then attach the container:

```yaml
services:
  windows:
    container_name: windows
    # ...
    networks:
      vlan:
        ipv4_address: 192.168.0.100

networks:
  vlan:
    external: true
```

With macvlan, **all ports are exposed automatically**, so you no longer need the `ports:` mapping.

> [!IMPORTANT]
> Because of how macvlan works, the Docker host itself **cannot reach** the container's macvlan IP. If host-to-container access matters, create a second macvlan bridge as a workaround.

### 8.2 Let Windows get an IP from your router (DHCP)

After setting up macvlan, you can have Windows itself request an address from your router — so the container and Windows each have their own LAN IP:

```yaml
environment:
  DHCP: "Y"
devices:
  - /dev/vhost-net
device_cgroup_rules:
  - 'c *:* rwm'
```

---

## 9. Connecting to Windows

There are two ways to reach the running machine.

### 9.1 Web viewer (port 8006)

Open **[http://127.0.0.1:8006/](http://127.0.0.1:8006/)** in any browser. This viewer is intended mainly for watching the installation and for quick access. Its picture quality is reduced and it has **no audio and no clipboard**.

### 9.2 Remote Desktop / RDP (port 3389)

For day-to-day use, connect with any Microsoft Remote Desktop client to the container's IP using username `Docker` and password `admin` (or your configured credentials). RDP gives you full resolution, audio, and clipboard.

| Platform | RDP client |
|---|---|
| Windows | Type `mstsc` in the search box |
| macOS / iOS | Microsoft Remote Desktop (App Store) |
| Android | Microsoft Remote Desktop (Play Store) |
| Linux | [FreeRDP](https://www.freerdp.com/) |

> [!TIP]
> If RDP refuses to connect even though Windows is running, your host may require the additional `NET_RAW` capability. Add `- NET_RAW` under `cap_add` alongside `NET_ADMIN`.

---

## 10. Advanced Usage

### 10.1 Verify that your system supports KVM

On a Linux host:

```bash
sudo apt install cpu-checker
sudo kvm-ok
```

```text
INFO: /dev/kvm exists
KVM acceleration can be used
```

If `kvm-ok` reports that KVM cannot be used, check that:

- Virtualization extensions (**Intel VT-x** or **AMD SVM**) are enabled in your BIOS/UEFI.
- **Nested virtualization** is enabled if the container runs inside another VM.
- You are not on a cloud VPS that blocks nested virtualization.

If `kvm-ok` succeeds but the container still reports a missing KVM device, add `privileged: true` to your compose file (or `sudo` to your `docker` command) to rule out a permission issue.

### 10.2 Install a custom ISO

Point `VERSION` at any ISO URL to download an unsupported image:

```yaml
environment:
  VERSION: "https://example.com/win.iso"
```

Or skip downloading entirely and bind a local ISO file:

```yaml
volumes:
  - ./example.iso:/boot.iso
```

When you bind `/boot.iso`, the `VERSION` value is ignored.

### 10.3 Run a script after installation

Place an `install.bat` (plus any files it needs) in a folder and bind it to `/oem`:

```yaml
volumes:
  - ./example:/oem
```

The folder is copied to `C:\OEM` inside Windows, and `install.bat` runs automatically as the final step of installation — ideal for unattended software setup.

### 10.4 Pass through a USB device

Find the device's vendor and product IDs with `lsusb`, then:

```yaml
environment:
  ARGUMENTS: "-device usb-host,vendorid=0x1234,productid=0x1234"
devices:
  - /dev/bus/usb
```

> [!WARNING]
> Do **not** attach a USB mass-storage device before Windows Setup has finished. Doing so can fail the install, or worse, the drive may be formatted as the system disk and its data lost. Keep USB storage disconnected on first launch.

### 10.5 Stopping the container gracefully

The compose example sets `stop_grace_period: 2m` (and the CLI uses `--stop-timeout 120`). This gives Windows time to perform a clean ACPI shutdown when you run `docker compose down` or `docker stop`. Keep this generous timeout so Windows is not forcibly killed mid-write.

```text
❯ Received SIGTERM, sending ACPI shutdown signal...
❯ Waiting for Windows to shut down... (3/110)
❯ Shutdown completed!
```

---

## 11. Troubleshooting

The format below lists the message or symptom, its cause, and the fix.

### Symptom: `pull access denied` when starting the container

**Cause:** The image name is `dockurr/windows` — note the **double "r"** in `dockurr`. Using `dockur/windows` (the GitHub org name) as the image name fails.
**Solution:** Use `image: dockurr/windows` exactly.
**Prevention:** Copy the image name directly from the [Quick Start](#1-quick-start) compose file.

### Symptom: Container exits immediately / complains about a missing KVM device

**Cause:** The host lacks KVM acceleration, or the container cannot access `/dev/kvm`.
**Solution:** Run `sudo kvm-ok` ([Section 10.1](#101-verify-that-your-system-supports-kvm)). Enable VT-x/SVM in BIOS, enable nested virtualization if inside a VM, or add `privileged: true` to rule out permissions.
**Prevention:** Verify KVM support before deploying.

### Symptom: RDP will not connect though the web viewer works

**Cause:** On some hosts RDP requires the `NET_RAW` capability in addition to `NET_ADMIN`.
**Solution:** Add `- NET_RAW` under `cap_add`.
**Prevention:** Include both capabilities when you intend to use RDP.

### Symptom: `BdsDxe: failed to load Boot0002 ... Not Found` appears in the logs

**Cause:** This is a harmless UEFI firmware message printed before the boot device is selected. The container already filters it from the viewer, but it can surface in raw container logs.
**Solution:** No action needed if Windows then boots. The installation continues normally.
**Prevention:** Not applicable — the message is cosmetic.

### Symptom: `Error: VERSION ... setup has failed to validate the product key`

**Cause:** Some trimmed community images (for example `tiny11`) ship without a valid embedded product key, so Setup's key validation fails.
**Solution:** Use one of the standard `VERSION` values from [Section 3](#3-selecting-a-windows-version), or supply a valid key via the `KEY` variable.
**Prevention:** Prefer the official editions for unattended installs.

### Symptom: Shared folder shows file-synchronization errors

**Cause:** The host bind-mounted folder has restrictive permissions or, on SELinux systems, the wrong security label.
**Solution:** Ensure the host folder is writable. On SELinux hosts, add the `:Z` flag to the bind mount, e.g. `- ./example:/shared:Z`.
**Prevention:** Create the shared folder with appropriate ownership before first start.

### Symptom: The account is locked and you cannot log in

**Cause:** Repeated failed logons (often via exposed RDP) can trigger the Windows account-lockout policy.
**Solution:** Wait for the lockout to expire, or reset the policy from inside Windows. Avoid exposing port `3389` to the public internet.
**Prevention:** Keep RDP on a private network; use a strong `PASSWORD`.

### Symptom: Host (especially a laptop) overheats during installation

**Cause:** The install phase is CPU-intensive and uses all assigned cores.
**Solution:** Lower `CPU_CORES` so the host retains thermal headroom.
**Prevention:** Assign fewer cores than the host has, particularly on laptops.

### Symptom: Windows hangs on a spinning startup circle (nested in ESXi/another hypervisor)

**Cause:** Nested virtualization is not fully enabled on the outer hypervisor.
**Solution:** Enable nested virtualization (and "expose hardware-assisted virtualization") on the outer VM, then restart.
**Prevention:** Confirm `kvm-ok` passes inside the outer VM first.

### Symptom: Network drops with a `passt`/`SIGABRT` crash

**Cause:** A known incompatibility between the `passt` user-mode networking backend and certain CPUs.
**Solution:** Switch to bridge or macvlan networking ([Section 8](#8-networking)).
**Prevention:** Use bridge networking unless you specifically need user-mode networking.

---

## 12. FAQ

**Q: Do I need a Windows license or product key?**
A user account and a trial/generic key are configured automatically so installation completes without prompting. For production or activated use, supply your own license and the `KEY` variable. Any keys present in the project are generic placeholders published by Microsoft for trial purposes.

**Q: Is this project legal?**
Yes. The project contains only open-source code and distributes no copyrighted material — the Windows media is downloaded at runtime directly from Microsoft's own servers (or public mirrors). The product names and logos are trademarks of their respective holders; the project is not affiliated with or endorsed by Microsoft.

**Q: Where does the Windows image come from?**
On first run, the container downloads the official ISO/ESD from Microsoft's servers. If those are unreachable, it falls back to public mirror hosts. You can also supply your own ISO ([Section 10.2](#102-install-a-custom-iso)).

**Q: Will my installation survive a container restart or recreation?**
Yes, as long as `/storage` is bound to a host folder or named volume. The disk image and all settings are kept there.

**Q: Can I run multiple Windows containers at once?**
Yes. Give each its own `container_name`, its own `/storage` folder, and distinct published host ports (e.g. `8007:8006`).

**Q: How do I get higher graphics quality, audio, or clipboard?**
Connect over RDP ([Section 9.2](#92-remote-desktop--rdp-port-3389)) rather than the web viewer.

**Q: Can I run macOS or Linux this way instead?**
Yes — use [`dockur/macos`](https://github.com/dockur/macos) for macOS or [`qemus/qemu`](https://github.com/qemus/qemu) for a Linux desktop. They share much of the same machinery.

**Q: How do I run ARM64 Windows?**
Use [`dockur/windows-arm`](https://github.com/dockur/windows-arm). This `dockur/windows` image is for x64 hosts and x64 Windows.

**Q: The installation finished — can I delete the downloaded ISO?**
The container removes the installation ISO automatically once setup completes (unless you set `REMOVE: "N"`), so you don't need to manage it.

---

_Generated June 2026 from dockur/windows at 0c9472b. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
