---
title: "Running Windows Workloads on Kubernetes: Node Pools, Scheduling, and Gotchas"
description: "How to add Windows node pools to a Kubernetes cluster, schedule workloads with OS-specific selectors and taints, handle networking differences, and avoid common Windows container pitfalls."
url: https://agent-zone.ai/knowledge/kubernetes/kubernetes-windows-nodes/
section: knowledge
date: 2026-02-22
categories: ["kubernetes"]
tags: ["windows","windows-containers","node-selector","taints","hybrid-cluster","containerd","eks","aks","gke","iis","dotnet"]
skills: ["windows-node-management","hybrid-cluster-scheduling","os-specific-deployment","windows-container-troubleshooting"]
tools: ["kubectl","crictl","az","aws","gcloud"]
levels: ["intermediate"]
word_count: 1294
formats:
  json: https://agent-zone.ai/knowledge/kubernetes/kubernetes-windows-nodes/index.json
  html: https://agent-zone.ai/knowledge/kubernetes/kubernetes-windows-nodes/?format=html
  api: https://api.agent-zone.ai/api/v1/knowledge/search?q=Running+Windows+Workloads+on+Kubernetes%3A+Node+Pools%2C+Scheduling%2C+and+Gotchas
---


# Running Windows Workloads on Kubernetes

Kubernetes supports Windows worker nodes alongside Linux worker nodes in the same cluster. This enables running Windows-native applications -- .NET Framework services, IIS-hosted applications, Windows-specific middleware -- on Kubernetes without rewriting them for Linux. However, Windows nodes are not interchangeable with Linux nodes. There are fundamental differences in networking, storage, container runtime behavior, and resource management that you must account for.

## Core Constraints

Before adding Windows nodes, understand what is and is not supported:

**Supported:**
- Windows Server 2019 and 2022 as worker nodes
- Windows containers (process-isolated and Hyper-V isolated)
- containerd as the container runtime (Docker is deprecated for Kubernetes)
- Most pod spec features: resource requests/limits, liveness/readiness probes, ConfigMaps, Secrets, ServiceAccounts
- Deployments, StatefulSets, DaemonSets, Jobs, CronJobs

**Not supported:**
- Windows control plane nodes. The control plane (API server, etcd, scheduler, controller-manager) must always run on Linux.
- Privileged containers. Windows does not support the Linux privileged mode.
- Host networking (`hostNetwork: true`). Not available on Windows.
- Certain volume types: hostPath works but behaves differently. Linux-specific volume types (cephfs, glusterfs) are unsupported.
- Linux capabilities (`securityContext.capabilities`). Windows uses a different security model.
- Running Linux containers on Windows nodes (and vice versa).

## Adding Windows Node Pools

### EKS

```bash
aws eks create-nodegroup \
  --cluster-name my-cluster \
  --nodegroup-name windows-workers \
  --node-role arn:aws:iam::123456789:role/EKSWindowsNodeRole \
  --ami-type WINDOWS_CORE_2022_x86_64 \
  --instance-types m5.2xlarge \
  --scaling-config minSize=1,maxSize=5,desiredSize=2 \
  --subnets subnet-abc123 subnet-def456
```

EKS requires the `vpc-resource-controller` and `vpc-admission-webhook` addons for Windows pod networking (installed automatically in newer EKS versions).

### AKS

AKS has the strongest native Windows support. It automatically applies `kubernetes.io/os=windows` labels. Add a taint to prevent Linux pods from scheduling on Windows nodes:

```bash
az aks nodepool add \
  --resource-group myRG --cluster-name myCluster \
  --name winnp --os-type Windows --os-sku Windows2022 \
  --node-count 2 --node-vm-size Standard_D4s_v5 \
  --node-taints os=windows:NoSchedule
```

### GKE

```bash
gcloud container node-pools create win-pool \
  --cluster=my-cluster --zone=us-central1-a \
  --image-type=WINDOWS_LTSC_CONTAINERD \
  --machine-type=n1-standard-4 --num-nodes=2 \
  --node-taints=os=windows:NoSchedule
```

## Scheduling: Ensuring Pods Land on the Right OS

This is the most critical configuration for hybrid Linux/Windows clusters. Without proper scheduling constraints, Linux pods may attempt to schedule on Windows nodes (and fail) or vice versa.

### nodeSelector (Simple Approach)

Every Kubernetes node is automatically labeled with `kubernetes.io/os`. Use `nodeSelector` to target the correct OS:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: iis-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: iis-app
  template:
    metadata:
      labels:
        app: iis-app
    spec:
      nodeSelector:
        kubernetes.io/os: windows
      containers:
      - name: iis
        image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2022
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: "500m"
            memory: "1Gi"
          limits:
            cpu: "1"
            memory: "2Gi"
```

**Every pod in a hybrid cluster should have a nodeSelector for `kubernetes.io/os`.** This includes Linux pods. While Linux pods will naturally fail to run on Windows nodes, they may be scheduled there and enter CrashLoopBackOff, wasting scheduling cycles and potentially blocking Windows capacity.

```yaml
# Add this to ALL Linux deployments in hybrid clusters
spec:
  nodeSelector:
    kubernetes.io/os: linux
```

### Taints and Tolerations (Recommended Approach)

nodeSelector alone is passive -- it tells the scheduler where to go but does not prevent other pods from consuming resources on Windows nodes. Use taints on Windows nodes combined with tolerations on Windows workloads:

```bash
# Taint Windows nodes (most cloud providers let you do this at node pool creation)
kubectl taint nodes <windows-node> os=windows:NoSchedule
```

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dotnet-api
spec:
  replicas: 3
  template:
    spec:
      nodeSelector:
        kubernetes.io/os: windows
      tolerations:
      - key: os
        operator: Equal
        value: windows
        effect: NoSchedule
      containers:
      - name: api
        image: myregistry/dotnet-api:6.0
        ports:
        - containerPort: 5000
```

This dual constraint (nodeSelector plus toleration) is the belt-and-suspenders approach for hybrid clusters. The taint prevents Linux pods from landing on Windows nodes. The nodeSelector directs the Windows pod to the right node type.

### RuntimeClass for Container Isolation

Windows supports two container isolation modes. **Process isolation** shares the host kernel -- fast and lightweight but requires matching Windows versions between container image and host. **Hyper-V isolation** runs each container in a lightweight VM -- provides stronger isolation and allows version mismatches but has higher overhead. Configure Hyper-V isolation via a RuntimeClass with handler `runhcs-wcow-hypervisor` and reference it with `runtimeClassName` in the pod spec.

## Networking Differences

Windows networking on Kubernetes works differently from Linux in several important ways.

### Service Networking

Windows supports ClusterIP, NodePort, and LoadBalancer service types. However, the underlying implementation differs:

- **kube-proxy on Windows** uses the Host Networking Service (HNS) rather than iptables or IPVS. HNS rules can take longer to converge, especially with large numbers of services.
- **DNS** works the same way (CoreDNS runs on Linux nodes, Windows pods query it normally).
- **NetworkPolicy** support depends on the CNI plugin. Calico for Windows supports NetworkPolicies. The default Windows CNI (win-bridge or win-overlay) does not.

### CNI Plugin Considerations

| CNI | Windows Support | NetworkPolicy | Notes |
|---|---|---|---|
| **Azure CNI** | Full | Via Calico | Default for AKS, best Windows experience |
| **Calico** | Full | Yes | Requires Calico for Windows components |
| **Flannel** (win-overlay) | Partial | No | Overlay networking, simpler setup |
| **Amazon VPC CNI** | Via resource controller | Limited | EKS-specific implementation |

### No HostPort or HostNetwork

Windows nodes do not support `hostPort` or `hostNetwork: true` in pod specs. If your Linux deployment uses either of these, the Windows equivalent must use a different approach -- typically a NodePort or LoadBalancer service.

## Storage on Windows Nodes

Windows nodes support emptyDir (NTFS), hostPath (must use Windows paths like `C:\data`), ConfigMap/Secret mounts, and PersistentVolumeClaims (via CSI drivers). Azure Disk and Azure File work natively on AKS. EBS works via the EBS CSI driver on EKS. NFS has limited support requiring a Windows NFS client. Important: if your Helm chart hardcodes Linux paths (e.g., `/var/data`), it will fail on Windows nodes.

## Container Image Considerations

### Base Image Matching

Windows container images must match the host OS version when using process isolation. Running a `ltsc2019` container on a `ltsc2022` host fails with process isolation (but works with Hyper-V isolation).

| Host OS | Process-Isolated Images | Hyper-V Images |
|---|---|---|
| Windows Server 2022 (LTSC) | ltsc2022 only | ltsc2019, ltsc2022 |
| Windows Server 2019 (LTSC) | ltsc2019 only | ltsc2019 only |

Use multi-arch manifest lists to support multiple Windows versions:

```dockerfile
# Dockerfile for Windows (ltsc2022)
FROM mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-ltsc2022
WORKDIR /app
COPY publish/ .
ENTRYPOINT ["dotnet", "MyApi.dll"]
```

### Image Size

Windows container images are substantially larger than Linux. Nano Server is approximately 280 MB, Server Core is approximately 4.7 GB, and the full Windows image is approximately 9 GB (compared to 5 MB for Alpine Linux). Use `nanoserver` for .NET applications. Use `servercore` only when Win32 APIs or IIS are required. Factor large image pull times into readiness probe initial delays and scaling expectations.

## Resource Management Differences

### CPU and Memory

Windows containers support CPU and memory requests and limits, but the enforcement mechanisms differ:

- **CPU limits** on Windows use Windows Job Objects, which enforce hard CPU caps. There is no CFS-based throttling like Linux.
- **Memory limits** trigger container termination (similar to Linux OOM kill) when exceeded.
- **No swap** support in Windows containers.

### Resource Recommendations

Windows nodes have higher baseline resource consumption than Linux. The OS and system services consume approximately 3.5 GB memory and 1.2 CPU, compared to roughly 1.3 GB and 0.6 CPU on Linux. A 4-CPU, 16GB Windows node has roughly 2.8 CPU and 12.5GB available for workloads, compared to 3.4 CPU and 14.7GB on an equivalent Linux node. Size Windows node pools accordingly.

## Common Gotchas

**CrashLoopBackOff with no useful logs.** Windows container crashes often produce less informative errors than Linux. Check Windows Event Logs inside the container with `kubectl exec <pod> -- powershell Get-EventLog -LogName Application -Newest 20`.

**Service account token mounting.** Windows mounts the token at `C:\var\run\secrets\kubernetes.io\serviceaccount`. Most client libraries handle this automatically, but custom code must use the correct path.

**Container restart latency.** Windows containers take 10-30 seconds longer to start than Linux. Use startup probes with generous `failureThreshold` values to handle slow-starting Windows applications.

**Windows nodes cannot run standard DaemonSets.** Most cluster addons (Prometheus node-exporter, Fluentd, Falco) only work on Linux. Use `windows_exporter` for Prometheus node metrics and Fluent Bit for logging on Windows nodes.

**Group Managed Service Accounts (gMSA).** Windows workloads needing Active Directory authentication require gMSA configuration with additional CRDs and webhook admission controllers.

