How to Install and Configure Kubernetes (k3s) on Windows Server 2025

Running Kubernetes workloads that include Windows containers requires a hybrid cluster — Linux nodes handle the Kubernetes control plane and Linux workloads, while Windows worker nodes join the cluster to run Windows Server container pods. k3s, a lightweight Kubernetes distribution from Rancher, makes this topology straightforward: a Linux VM or WSL2 instance hosts the k3s server, and a Windows Server 2025 machine joins as a worker node using the community-maintained sig-windows-tools components. This guide walks through standing up the full hybrid cluster, configuring the Flannel CNI for Windows, deploying a Windows Server pod, and managing the cluster with kubectl and Helm from a Windows administrator workstation.

Prerequisites

  • Windows Server 2025 worker node with Docker Engine installed and at least 4 GB of RAM
  • A Linux host for the k3s control plane — either a separate VM (Ubuntu 22.04 recommended) or WSL2 on a Windows development machine
  • Network connectivity between the Linux control plane and the Windows worker node (TCP 6443, UDP 8472 for VXLAN)
  • PowerShell 7.x on the Windows node (install via winget install Microsoft.PowerShell)
  • Chocolatey or winget for installing CLI tools on Windows
  • Administrator/root privileges on both nodes

Step 1: Install k3s on the Linux Control Plane

The k3s control plane must run on Linux. If you are working in a Windows Server-only environment, enable WSL2 and install Ubuntu, or provision a small Linux VM on Hyper-V. The following commands run on the Linux host.

# --- Run on the Linux control plane host ---

# Install k3s with Flannel VXLAN backend (required for Windows node compatibility)
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server 
  --flannel-backend=vxlan 
  --disable=traefik 
  --node-label kubernetes.io/os=linux" sh -

# Verify k3s is running
sudo systemctl status k3s

# Get the server token (needed for worker nodes to join)
sudo cat /var/lib/rancher/k3s/server/node-token

# Get the control plane API server IP (note this for Windows worker join)
hostname -I | awk '{print $1}'

# Export kubeconfig for remote kubectl access
sudo cat /etc/rancher/k3s/k3s.yaml
# Replace 127.0.0.1 with the real IP in the output before copying to Windows

Step 2: Install kubectl on Windows Server 2025

Install the kubectl CLI on the Windows Server 2025 node so you can interact with the cluster from PowerShell. Chocolatey provides the most straightforward installation path on a server that does not have winget.

# --- Run on Windows Server 2025 (PowerShell as Administrator) ---

# Install Chocolatey if not already present
Set-ExecutionPolicy Bypass -Scope Process -Force
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))

# Install kubectl via Chocolatey
choco install kubernetes-cli -y

# Verify kubectl version
kubectl version --client

# Alternatively, download kubectl directly from the Kubernetes release site
$KubeVersion = "v1.30.1"
Invoke-WebRequest `
    -Uri "https://dl.k8s.io/release/$KubeVersion/bin/windows/amd64/kubectl.exe" `
    -OutFile "C:WindowsSystem32kubectl.exe"

kubectl version --client

Step 3: Configure kubeconfig on Windows

Copy the kubeconfig from the Linux control plane to the Windows node so kubectl can authenticate to the API server. Replace 127.0.0.1 in the kubeconfig with the real IP address of the Linux node before saving it.

# Create the .kube directory
$KubeDir = "$env:USERPROFILE.kube"
New-Item -ItemType Directory -Force -Path $KubeDir

# Paste the kubeconfig content from the Linux node
# (copy the output of: sudo cat /etc/rancher/k3s/k3s.yaml on Linux)
# Save it here, replacing 127.0.0.1 with the Linux node IP
$LinuxNodeIP = "10.0.0.10"   # Replace with your actual IP

$KubeconfigContent = @"
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: 
    server: https://${LinuxNodeIP}:6443
  name: default
contexts:
- context:
    cluster: default
    user: default
  name: default
current-context: default
kind: Config
preferences: {}
users:
- name: default
  user:
    client-certificate-data: 
    client-key-data: 
"@

$KubeconfigContent | Set-Content -Path "$KubeDirconfig" -Encoding UTF8

# Set the KUBECONFIG environment variable
$env:KUBECONFIG = "$KubeDirconfig"
[Environment]::SetEnvironmentVariable("KUBECONFIG", "$KubeDirconfig", "Machine")

# Test connectivity to the control plane
kubectl cluster-info
kubectl get nodes

Step 4: Prepare the Windows Worker Node

Before joining the Windows node to k3s, install the required Windows container components and configure the node. The sig-windows-tools project from the Kubernetes community provides wins.exe, a privileged container helper, and the Flannel CNI plugin for Windows.

# Install the Windows Containers feature (if not already installed with Docker)
Install-WindowsFeature -Name Containers -Restart

# After reboot, install the Hyper-V feature for Hyper-V isolated pods
Install-WindowsFeature -Name Hyper-V -IncludeManagementTools -Restart

# Download sig-windows-tools for k3s / Kubernetes 1.30
$SigWindowsRelease = "v0.2.0"
$SigBaseUrl = "https://github.com/kubernetes-sigs/sig-windows-tools/releases/download/$SigWindowsRelease"

New-Item -ItemType Directory -Force -Path C:k

# Download wins.exe (Windows privileged helper binary)
Invoke-WebRequest -Uri "$SigBaseUrl/wins.exe" -OutFile C:kwins.exe

# Download kube-proxy.exe
Invoke-WebRequest -Uri "$SigBaseUrl/kube-proxy.exe" -OutFile C:kkube-proxy.exe

# Install wins as a Windows service
C:kwins.exe srv app run --path ./

sc.exe create wins binPath= "C:kwins.exe srv app run --path ./" start= auto
sc.exe start wins

Step 5: Configure Flannel CNI for Windows

Flannel VXLAN is the only CNI supported by k3s for Windows nodes. The Windows Flannel configuration must match the CIDR and backend settings used on the Linux control plane.

# Download the Flannel CNI plugin for Windows
$FlannelRelease = "v0.25.1"
Invoke-WebRequest `
    -Uri "https://github.com/flannel-io/flannel/releases/download/$FlannelRelease/flannel-windows-amd64.exe" `
    -OutFile C:kflanneld.exe

# Create the Flannel configuration directory
New-Item -ItemType Directory -Force -Path C:etckube-flannel

# Write the Flannel network config (must match the k3s cluster CIDR)
@'
{
  "Network": "10.244.0.0/16",
  "Backend": {
    "Type": "vxlan",
    "VNI": 4096,
    "Port": 4789
  }
}
'@ | Set-Content -Path C:etckube-flannelnet-conf.json -Encoding UTF8

# Open required firewall ports for Flannel VXLAN
New-NetFirewallRule -DisplayName "Flannel VXLAN" -Direction Inbound `
    -Protocol UDP -LocalPort 4789 -Action Allow

New-NetFirewallRule -DisplayName "Kubernetes API" -Direction Inbound `
    -Protocol TCP -LocalPort 6443 -Action Allow

New-NetFirewallRule -DisplayName "kubelet" -Direction Inbound `
    -Protocol TCP -LocalPort 10250 -Action Allow

Step 6: Join the Windows Node to the k3s Cluster

k3s does not natively support Windows agent nodes — the k3s agent binary is Linux-only. The standard approach is to run the Kubernetes node components (kubelet.exe, kube-proxy.exe) directly on the Windows host using the sig-windows-tools scripts that wrap the node join process.

# Download the kubelet binary for Windows
$K8sVersion = "v1.30.1"
Invoke-WebRequest `
    -Uri "https://dl.k8s.io/release/$K8sVersion/bin/windows/amd64/kubelet.exe" `
    -OutFile C:kkubelet.exe

# Set cluster join variables
$K3sServerIP = "10.0.0.10"     # Linux control plane IP
$NodeToken   = "K10abc..."      # Token from: sudo cat /var/lib/rancher/k3s/server/node-token

# Generate a kubeconfig for the kubelet on this node
# (In production, use kubeadm token or the k3s join token mechanism)
kubectl --kubeconfig $env:KUBECONFIG config set-cluster k3s-cluster `
    --server "https://${K3sServerIP}:6443" `
    --insecure-skip-tls-verify=false

# Start kubelet with Windows node labels
C:kkubelet.exe `
    --kubeconfig="$env:USERPROFILE.kubeconfig" `
    --hostname-override=$(hostname) `
    --node-labels="kubernetes.io/os=windows,node.kubernetes.io/windows-build=10.0.20348" `
    --container-runtime-endpoint="npipe:////./pipe/docker_engine" `
    --cgroups-per-qos=false `
    --enforce-node-allocatable="" `
    --resolv-conf="" `
    --log-dir=C:klogs `
    --logtostderr=false

# After kubelet starts, verify the node appears in the cluster
kubectl get nodes -o wide

Step 7: Deploy a Windows Server Container Pod

With the Windows worker node registered, deploy a pod that is scheduled specifically on the Windows node using a nodeSelector for kubernetes.io/os: windows.

# Create a Windows Server pod manifest
@'
apiVersion: v1
kind: Pod
metadata:
  name: win-servercore-test
  labels:
    app: win-test
spec:
  nodeSelector:
    kubernetes.io/os: windows
  tolerations:
  - key: "os"
    operator: "Equal"
    value: "windows"
    effect: "NoSchedule"
  containers:
  - name: servercore
    image: mcr.microsoft.com/windows/servercore:ltsc2022
    command: ["powershell.exe", "-Command", "while ($true) { Start-Sleep 30 }"]
    resources:
      limits:
        memory: "512Mi"
        cpu: "500m"
'@ | Set-Content -Path C:kwin-pod.yaml -Encoding UTF8

kubectl apply -f C:kwin-pod.yaml

# Watch the pod scheduling
kubectl get pods -w

# Execute a command inside the Windows pod
kubectl exec -it win-servercore-test -- powershell.exe -Command `
    "[System.Environment]::OSVersion.VersionString"

Step 8: Install Helm on Windows Server 2025

Helm is the package manager for Kubernetes and simplifies deploying complex applications from community-maintained charts. Install Helm on the Windows administrator node to manage cluster-wide applications.

# Install Helm via Chocolatey
choco install kubernetes-helm -y

# Verify Helm installation
helm version

# Add the stable and Bitnami chart repositories
helm repo add stable https://charts.helm.sh/stable
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

# List available charts (showing Linux-compatible charts)
helm search repo stable --max-col-width 60

# Deploy a chart to Linux nodes (using node affinity in values)
helm install nginx bitnami/nginx `
    --set nodeSelector."kubernetes.io/os"=linux `
    --namespace default

# List deployed Helm releases
helm list --all-namespaces

Conclusion

Building a hybrid Kubernetes cluster with k3s and Windows Server 2025 worker nodes requires careful coordination between the Linux control plane, the Windows container runtime, and the Flannel CNI layer. This guide covered installing k3s on Linux, configuring kubectl and helm on Windows, preparing the Windows worker node with the sig-windows-tools binaries, joining the node to the cluster, and scheduling Windows-specific pods with the correct nodeSelector. While the setup involves more manual steps than a pure Linux cluster, the resulting hybrid environment supports running Windows Server 2025 containerised workloads alongside Linux microservices in a single Kubernetes cluster — a common requirement in organisations transitioning legacy Windows applications to container-based infrastructure. For production deployments, consider AKS hybrid (Azure Kubernetes Service on Azure Stack HCI) as a fully supported path for Windows container workloads on Kubernetes.