← Back to Blog

Kubernetes, Docker or Podman for Full Windows in a Container, and Where pasta Fits

· 14 min read Three plain glowing blocks on the floor of a dark server room, a thin amber line looping past a server rack and a blank glowing window panel.

Our Windows 11 desktop runs in a container on a rented Linux server. Docker starts it today, and three Podman versions started the same container without a change to its flags. The next question comes from anyone who runs Kubernetes at work: why not schedule it there, next to everything else?

The honest answer starts with a vocabulary problem. "Windows in a container" names three different things, and only two of them give you a full Windows desktop. This post separates them, then covers what Kubernetes adds over Docker and Podman for the one that matters here, what it costs a server, and where pasta, the user-mode network tool now under Podman, QEMU, libvirt and KubeVirt, fits in.

We did not run Kubernetes on our server for this article. The Docker and Podman results are our own. The Kubernetes and KubeVirt sections rest on their documentation and source code, quoted below. The warning section explains why we kept a kubelet off a server that someone works on.

Three things called "Windows in a container"

Scroll to compare columns and rows when the table extends beyond the box.

Kind Windows 11 desktop? Runs on
Windows containers [1] No Windows Server
Windows VM in a Linux container [2] Yes Linux with KVM
KubeVirt virtual machine [3] Yes Kubernetes with KVM
  1. Windows containers: Windows Server user space sharing the host's Windows kernel. Typical use: IIS or a .NET Framework app. Run by a container engine on Windows Server, or by Kubernetes with Windows nodes.
  2. Windows VM in a Linux container: a full Windows install under QEMU and KVM, packaged as a container image such as dockur/windows. Docker, Podman or Kubernetes can run it.
  3. KubeVirt virtual machine: a full Windows install under QEMU and KVM, run in a pod and managed as a Kubernetes VirtualMachine object. Needs Kubernetes plus the KubeVirt add-on.

Windows containers are not a Windows desktop. Kubernetes has supported Windows worker nodes since version 1.14 in 2019. Its Windows guide sets the limits:

  • "you can only run the control plane on Linux".
  • Windows nodes must run Windows Server 2022 or Windows Server 2025.
  • "Kubernetes does not support running Windows containers with Hyper-V isolation."
  • Privileged containers are "not supported for Windows containers"; HostProcess containers are the substitute.

The images come from Microsoft. Its fullest base image, called simply "Windows", "contains the full set of Windows APIs and system services (minus server roles)", and "The Windows image isn't available for Windows Server 2025." The host and image must also agree on version. Microsoft's compatibility page says Windows Server containers "are blocked from starting when the build number between the container host and the container image are different", with one exception for Windows Server 2022 images on Windows 11.

We found no Microsoft page that says "no desktop" in so many words. In practice, a Windows container is a server process, not a session you sign in to over Remote Desktop. Use Windows containers for Windows server software; they are not a way to host Windows 11.

The other two columns both run a complete Windows install as a virtual machine. They differ in who owns the virtual machine: a container image you bring (dockur), or Kubernetes itself (KubeVirt).

What a Windows VM container needs from any engine

dockur's requirement is "Docker or Podman on a Linux host with KVM support". Under any engine, the container needs the same five things:

  • The KVM device (/dev/kvm), so Windows runs at hardware speed.
  • A TUN device (/dev/net/tun) and the NET_ADMIN capability, so the container can build its own network for the VM.
  • A persistent disk.
  • About two minutes to shut down, so Windows closes cleanly.
  • Ports kept private.

Docker and Podman grant each of those with one flag: --device /dev/kvm, --device /dev/net/tun, --cap-add NET_ADMIN, a volume and --stop-timeout 120.

Kubernetes has no --device flag in a pod spec. dockur publishes a Kubernetes manifest, installed with one kubectl apply. It gets the devices the blunt way: the container is privileged: true, adds NET_ADMIN, and mounts both devices from the node as hostPath volumes of type CharDevice. The rest is sensible:

  • Disk: a 64 Gi ReadWriteOnce claim.
  • Restarts: a single-replica Deployment with the Recreate strategy, so two copies never hold the disk at once.
  • Shutdown: terminationGracePeriodSeconds: 120, matching the two minutes Windows needs.
  • Network: a ClusterIP Service, so nothing is published outside the cluster.

That pod fails every relevant check in Kubernetes' Pod Security Standards. The "baseline" profile, the middle of the three, says:

  • "Privileged Pods disable most security mechanisms and must be disallowed."
  • "HostPath volumes must be forbidden."
  • "Adding additional capabilities beyond those listed below must be disallowed", and NET_ADMIN is not on that list.

To run it, the namespace has to allow the "privileged" profile. On a cluster that other people use, that is a decision for whoever owns the cluster, not a detail of the deployment.

Kubernetes does have a cleaner mechanism for hardware. The device plugin framework, stable since 1.26, lets a node "advertise system hardware resources to the Kubelet", the way GPUs appear as nvidia.com/gpu. We found no general-purpose KVM device plugin in Kubernetes' own documentation. KubeVirt brings one.

Two cheap fixes apply to the manifest as published:

  • Pin the image. It names dockurr/windows with no tag, so a restart can pull a different release onto a disk installed by an older one. Pin it by digest.
  • Keep the Service internal. ClusterIP is the right default; reach the desktop through your own gateway rather than a LoadBalancer.

KubeVirt: virtual machines as Kubernetes objects

KubeVirt adds virtual machines to the Kubernetes API. It is a CNCF project, accepted on September 6, 2019 and incubating since April 19, 2022. Its latest release is v1.9.1, from October 12, 2026. Each running VM lives in its own virt-launcher pod. A node agent, virt-handler, publishes KVM as a schedulable resource named devices.kubevirt.io/kvm; KubeVirt's metrics count "nodes in the cluster that have the devices.kubevirt.io/kvm resource available." That is the device-plugin route that dockur's manifest replaces with privileged: true.

The installation guide says "Hardware with virtualization support is recommended". Without it, a useEmulation setting falls back to software emulation, which is far too slow for a desktop.

Windows 11 has specific needs, and KubeVirt documents them:

  • A TPM. "The Microsoft Windows 11 installer requires the presence of a TPM device, even though it doesn't use this." A VM gets one with tpm: {}, and it is "fully emulated (vTPM)", so the server needs no TPM chip of its own.
  • EFI and Secure Boot. "Enabling EFI automatically enables Secure Boot" unless you turn it off, and Secure Boot needs the SMM CPU feature.
  • Drivers. The VirtIO drivers for Windows ship as a container disk, and "Some drivers are required in the installation phase."
  • Install media. The Containerized Data Importer loads an ISO or disk image into a persistent volume claim.
  • Moving a running VM. Live migration needs storage with "shared ReadWriteMany (RWX) access mode". A single server's local disk is not that.

KubeVirt earns its extra components on a real cluster: several nodes, shared storage, and VMs that must move during maintenance. On one server, most of that has nothing to act on.

Networking, and where pasta fits

Every one of these setups stacks two network layers. The engine connects the container or pod. Then dockur builds a second network inside it for Windows. dockur "Supports NAT, user-mode, macvlan, and macvtap networking", and NAT is the default. A Windows VM in a Kubernetes pod therefore sits behind dockur's NAT, then the pod network, then a Service. That is fine for a desktop that mostly reaches out and receives Remote Desktop on one port.

The engine layer differs:

  • Docker uses a bridge and NAT built with iptables, with nftables support. Docker calls its default bridge "not recommended for production use"; a named network is the fix.
  • Podman uses Netavark when run as root and pasta when rootless. Pasta has been the rootless default since Podman 5.0, and Podman 6.0 removed the older slirp4netns (release notes).
  • Kubernetes hands the pod network to a CNI plugin, and Services give stable addresses. k3s, a small Kubernetes distribution, bundles Flannel for this.

pasta and passt are the same program. The project is "Plug A Simple Socket Transport" (passt) and "Pack A Subtle Tap Abstraction" (pasta):

  • passt gives a virtual machine a network made from the host's ordinary sockets.
  • pasta does the same for a network namespace, which is what a container is.
  • Privileges: neither needs "any capabilities or privileges".
  • No NAT by default: pasta copies the host's addresses into the namespace, "avoiding any need for NAT".
  • A drop-in successor: the project describes it as "bug-to-bug compatible" with slirp4netns, the tool it replaces.

That makes it the common thread across all four stacks:

Scroll to compare columns and rows when the table extends beyond the box.

Stack Role Since
Podman Rootless default 5.0
Docker Rootless, experimental Engine 25.0
QEMU VM network 7.2; native in 10.1
libvirt Interface backend 9.0.0
KubeVirt passtBinding (beta) v1.8.0

The details behind each row:

  • Podman: pasta became the default for rootless containers in Podman 5.0, in March 2024.
  • QEMU: -netdev stream with a separate passt process since QEMU 7.2; a native -netdev passt backend since QEMU 10.1.
  • libvirt: <backend type='passt'/> on a user-mode interface, since libvirt 9.0.0.
  • KubeVirt: the passtBinding network binding, beta since v1.8.0.

For Docker, its rootless documentation says "The pasta network driver is experimental, but provides improved throughput". You select it with DOCKERD_ROOTLESS_ROOTLESSKIT_NET=pasta. QEMU's own documentation says "passt can be used as a simple replacement for SLIRP (-net user). passt doesn't require any capability or privilege."

KubeVirt's passt binding needs the PasstBinding feature gate, "adds 250Mi of memory to the VM's resource requirements", and "automatically applies Network Address Translation (NAT)" for the guest.

What that means for a Windows VM. If you want no root anywhere, passt and pasta are how you get networking: rootless Podman, QEMU or libvirt run by a normal user, or KubeVirt's binding. If you run the engine as root on a server you control, as we do, you do not need them, and dockur's own NAT does the job. qemus/qemu, the base project dockur builds on, notes the cost of the user-mode route: with "user-mode networking (for example when running under Podman)", every extra port has to be listed in USER_PORTS.

Warning: a kubelet changes host-wide kernel settings

Read this before you run Kubernetes on a machine that does anything else. The kubelet, the Kubernetes agent on every node, tunes the kernel when it starts. Its configuration reference describes protectKernelDefaults: "if true, causes the Kubelet to error if kernel flags are not as it expects. Otherwise the Kubelet will attempt to modify kernel flags to match its expectation. Default: false."

The values it sets are in its source code, with the numbers in Kubernetes' sysctl helper:

  • vm.overcommit_memory = 1
  • vm.panic_on_oom = 0
  • kernel.panic = 10
  • kernel.panic_on_oops = 1
  • two kernel key limits

Those settings belong to the whole machine, not to a container. A kubelet inside a privileged container, which is how k3s in Docker, kind and k3d run, changes them for the host.

Before writing this, we checked our server: vm.overcommit_memory, kernel.panic and kernel.panic_on_oops were all 0. A Kubernetes test would have switched them to 1, 10 and 1 for as long as it ran. With panic_on_oops=1 and panic=10, any kernel oops reboots the server ten seconds later, along with the Windows desktop someone is working in.

Those values are sensible on a dedicated Kubernetes node, where failing fast and rebooting is the point. On a shared workstation they are a surprise. Give Kubernetes its own machine or virtual machine. If you must share, set the values yourself on purpose and turn on protectKernelDefaults, so the kubelet refuses to start rather than silently changing them.

What Kubernetes buys on one server, and what it costs

On a single server, Kubernetes gives you:

  • declarative files that also work on fifty nodes;
  • controllers that restart a failed pod;
  • persistent volume claims and Services with stable names;
  • one way of working if you already run a cluster.

It costs:

  • Memory and CPU for the control plane. k3s lists 2 cores and 2 GB for a server node.
  • A privileged pod or KubeVirt anyway. Neither escapes the need for KVM access.
  • The kernel settings above.
  • A third network layer.
  • A version treadmill. The project supports the three newest minor releases with about a year of patches each. Kubernetes 1.37 is current, and k3s v1.37.1 shipped on September 30, 2026.

What it does not give you on one server is failover. A ReadWriteOnce disk on local storage cannot follow the pod anywhere.

Docker Compose and Podman's Quadlet units cover the same single-server needs with less underneath:

  • a restart policy or a systemd service;
  • a named volume or a bind mount;
  • ports bound to 127.0.0.1;
  • the devices granted by name instead of a privileged container.

Licensing on hosted servers

dockur is direct about whose problem this is: "You are responsible for ensuring that you have a valid Windows license and that your use complies with Microsoft's licensing terms." Four Microsoft rules are worth knowing before you choose where Windows runs:

  • Dedicated hosts from the big four clouds. Since October 1, 2019, Microsoft's terms say licenses "purchased without Software Assurance and mobility rights cannot be deployed" on dedicated hosted cloud services from its Listed Providers: "Microsoft, Alibaba, Amazon (including VMware Cloud on AWS), and Google." If your dedicated server comes from one of them, read that page before bringing your own Windows license.
  • Multi-session Windows. "It's against the licensing agreement to run Windows multi-session outside of the Azure Virtual Desktop service for production purposes."
  • Windows containers. Microsoft's container FAQ ties the image license to "a validly licensed host OS". Windows Server Standard allows unlimited process-isolated containers and two Hyper-V-isolated ones. Running Windows containers on Windows 11 Pro or Enterprise is "for test or development purposes only".
  • Everything else depends on your edition and agreement. Microsoft's Product Terms are the reference, not a blog post, including this one.

How to choose

Scroll to compare columns and rows when the table extends beyond the box.

You want Use
One desktop, one server [1] Docker or Podman
Windows server apps at scale [2] Kubernetes, Windows nodes
Many VMs across nodes [3] KubeVirt
One desktop on your cluster [4] dockur's manifest
No root anywhere [5] Podman rootless, or passt
A shared workstation [6] Not Kubernetes
  1. One desktop, one server: Docker or Podman, run as root, with dockur. The devices are granted by name, with the least machinery.
  2. Windows server apps at scale: IIS or .NET Framework software belongs in Windows containers on Kubernetes Windows Server nodes, with a Linux control plane.
  3. Many VMs across nodes: KubeVirt on a cluster with shared storage gives you VM objects, a KVM device plugin and live migration.
  4. One desktop on your cluster: dockur's manifest in a namespace allowed the privileged profile, with the image pinned. It works with what you have, at a known security cost.
  5. No root anywhere: rootless Podman with pasta, or QEMU or libvirt with passt. User-mode networking needs no privileges, but we have not proven rootless Windows.
  6. A shared workstation: not Kubernetes, because of the kubelet's kernel settings.

For our server the answer stays Docker, with Podman as the tested alternative. It is one machine, one Windows desktop and one owner. Kubernetes would add a control plane, a privileged pod and host-wide kernel changes, and it would give no failover in return.

What this article is based on

  • Docker: the Windows 11 desktop in production on our server since October 14, 2026, described in Windows 11 in a Browser on Your Own Linux Server.
  • Podman: the same container started under Podman 5.7.0, 5.8.7 and a 6.2 development build on October 14, 2026, described in Podman or Docker?.
  • Kubernetes and KubeVirt: documentation and source code only. We did not run a kubelet on our production server, for the reasons in the warning above. The kernel values quoted there were read from that server, not changed on it.

Fact-check notes and sources

Related reading

This post is informational, not legal, licensing or security-consulting advice. Check Microsoft's Product Terms and your own agreements before running Windows on any hosted server. Mentions of Kubernetes, KubeVirt, k3s, Docker, Podman, passt, QEMU, libvirt, Microsoft, Amazon, Google, Alibaba, VMware and dockur are nominative fair use. No affiliation is implied.

← Back to Blog

Accessibility Options

Text Size
High Contrast
Reduce Motion
Reading Guide
Link Highlighting
Accessibility Statement

J.A. Watte is committed to ensuring digital accessibility for people with disabilities. This site conforms to WCAG 2.1 and 2.2 Level AA guidelines.

Measures Taken

  • Semantic HTML with proper heading hierarchy
  • ARIA labels and roles for interactive components
  • Color contrast ratios meeting WCAG AA (4.5:1)
  • Full keyboard navigation support
  • Skip navigation link
  • Visible focus indicators (3:1 contrast)
  • 44px minimum touch/click targets
  • Dark/light theme with system preference detection
  • Responsive design for all devices
  • Reduced motion support (CSS + toggle)
  • Text size customization (14px–20px)
  • Print stylesheet

Feedback

Contact: jwatte.com/contact

Full Accessibility Statement • Privacy Policy

Last updated: April 2026