Running Docker Containers on Windows Server: WSL2, Hyper-V Isolation, and Linux Containers

Windows Server can run containers, but the rules are different from the Linux world most Docker documentation assumes. There are two completely separate runtimes with different kernels, two isolation modes with different compatibility guarantees, and a Windows Server host has no Docker Desktop — you install the engine directly. This guide covers what actually works on a Windows Server VPS, how WSL2 fits in, and when Hyper-V isolation is mandatory rather than optional.

Container workloads drive a specific hardware profile: you need nested virtualization enabled and enough RAM to hold both the images and the build cache. If you are provisioning for this, our Windows VPS guide covers what to look for, and our guide to Windows VPS for remote development teams covers the build-agent side.

Two container worlds on one host

Windows containersLinux containers (via WSL2)
KernelShared Windows kernel (process isolation) or its own kernel in a utility VM (Hyper-V isolation)Microsoft’s Linux kernel in a lightweight Hyper-V utility VM
Base imagesmcr.microsoft.com/windows/servercore, nanoserver, dotnet/framework/aspnetAny Linux image (alpine, debian, ubuntu)
Image sizeNano Server ~100–300 MB, Server Core ~1.2–5 GBAlpine ~5 MB, Debian ~120 MB
Docker DesktopNot supported on Windows ServerNot supported on Windows Server
Run withDocker Engine (native Windows service) or containerddocker inside a WSL2 distribution, integrated with Windows Docker Engine
Host requirementContainers + Hyper-V featuresWSL2 (Server 2022/2025) + nested virtualization

The key point: Docker Desktop is not licensed or supported on Windows Server. On Server you install the Docker Engine service natively, or run the Linux engine inside a WSL2 distribution. Anything that says “install Docker Desktop” in a Windows Server tutorial is either wrong or is silently installing an unsupported configuration.

Installing the Windows container runtime

# 1. Enable the required features (Hyper-V is needed for Hyper-V isolation and WSL2)
Enable-WindowsOptionalFeature -Online -FeatureName Containers -All -NoRestart
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All -NoRestart
Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -All -NoRestart
# Reboot

# 2. Install the Docker Engine (moby) via the Microsoft-published package
Install-Module -Name DockerMsftProvider -Repository PSGallery -Force
Install-Package -Name docker -ProviderName DockerMsftProvider -Force
Start-Service docker

# 3. Verify
docker version
docker info --format '{{.OSType}} / {{.Isolation}} / {{.ServerVersion}}'

On a VPS, step 0 is verifying that the host allows nested virtualization. Hyper-V and Hyper-V isolation both need the vmx/svm exposure flag. If Get-ComputerInfo | Select HyperVRequirement* reports a missing hypervisor or the VirtualizationFirmwareEnabled flag is false, your provider has not enabled nested virtualisation and Hyper-V isolation simply will not start. Process isolation still works in that case, which is why it is the fallback for constrained environments.

Process isolation vs Hyper-V isolation

This is the single most misunderstood part of Windows containers.

  • Process isolation — containers share the host’s Windows kernel. Fast to start (sub-second), low memory overhead, but the host OS build must match or be newer than the image. You cannot run a ltsc2019 image on a Server 2022 host with process isolation. Mismatched versions fail with 0xc0000135 or a “no matching image for OS version” error.
  • Hyper-V isolation — each container gets its own kernel in a minimal utility VM. Starts in a few seconds and costs a few hundred MB of extra RAM, but it lets an older image run on a newer host, and it gives a stronger security boundary for untrusted code.
# Explicit isolation mode
docker run --isolation=process   -d --name web1 mcr.microsoft.com/windows/servercore:ltsc2022
docker run --isolation=hyperv    -d --name web2 mcr.microsoft.com/windows/servercore:ltsc2019

# Check the host's default and supported isolation
docker info --format '{{json .Isolation}}'

# Match image tag to your host build
[System.Environment]::OSVersion.Version   # e.g. 10.0.20348 = Server 2022 -> ltsc2022

A practical rule: use process isolation on a modern single-version fleet for speed and memory efficiency, and use Hyper-V isolation when you need to run legacy base images, when tenants are mutually untrusted, or when a container is exposed to untrusted input.

Running Linux containers with WSL2

WSL2 on Windows Server is available on Server 2022 and Server 2025 (Server 2019 only has WSL1, which is not suitable for Docker). Once installed, you can run the Linux engine inside the distribution and have it act as the Docker context for your Windows shell.

# Install WSL2 with a distribution (Server 2022+ / 2025)
wsl --install -d Ubuntu-22.04
wsl --set-default-version 2
wsl --update

# Inside the distribution, install the Linux engine
wsl -d Ubuntu-22.04 -- bash -lc "curl -fsSL https://get.docker.com | sh && sudo usermod -aG docker \$USER"

# Expose it to Windows Docker clients via a context
docker context create wsl-linux --docker "host=tcp://127.0.0.1:2375"
docker context use wsl-linux
docker run --rm hello-world

Two caveats dominate real deployments. First, WSL2 requires the same nested-virtualization support as Hyper-V isolation, so it will not start on many budget VPS plans. Second, the Linux engine inside WSL2 stores its images in the distribution’s virtual disk, which grows on demand and does not shrink automatically:

# Reclaim space when the WSL2 virtual disk balloons
wsl --shutdown
Optimize-VHD -Path "$env:LOCALAPPDATA\Packages\CanonicalGroupLimited*\LocalState\ext4.vhdx" -Mode Full

Windows container gotchas that cost hours

  • Image size. A Server Core base image is over 1 GB, and a full .NET Framework app image can reach 5–8 GB. Plan disk accordingly and prune aggressively with docker system prune -a.
  • No -v host volume semantics like Linux. Bind mounts work, but the container user’s permissions must match the host ACL, or you get access-denied inside the container.
  • Layer cache is build-version sensitive. Building a ltsc2022 image on a ltsc2019 host fails at the FROM layer. Pin your base image tags and pin your host build.
  • Networking. The default NAT network only exposes published ports. For container-to-container traffic on a VPS, use a user-defined network and reference containers by name, exactly as on Linux.
  • Server Core as the build base. Use a multi-stage Dockerfile — a nanoserver runtime stage on top of a servercore build stage can cut the final image by an order of magnitude.
# Multi-stage: big build stage, small runtime stage
FROM mcr.microsoft.com/dotnet/sdk:8.0-windowsservercore-ltsc2022 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-ltsc2022
COPY --from=build /app /app
ENTRYPOINT ["dotnet", "C:\\app\\MyApp.dll"]

Choosing the right approach

ScenarioRecommended approach
.NET Framework / full-trust Windows appWindows container, process isolation, Server Core base
Modern .NET 6/8 Linux-targeted serviceLinux container on WSL2 (or move to a Linux VPS)
Untrusted or third-party codeWindows container with Hyper-V isolation
Legacy base image on a new hostHyper-V isolation
VPS without nested virtualisationWindows containers, process isolation only

If your stack is mixed, the pragmatic pattern on a Windows VPS is to run Windows containers natively for the .NET Framework services and keep Linux containers in WSL2 for the supporting tooling, rather than trying to force one runtime to do both. Before you commit, verify nested virtualisation support with your provider — that single detail decides whether Hyper-V isolation and WSL2 are available at all, and it is worth confirming in writing before you compare Windows VPS options on price alone.

Related: How to deploy a .NET application on a Windows VPS and IIS application pool configuration for when containers are not the right answer.

Leave a Comment