Docker on Windows with WSL2: Complete Setup and Development Guide
Windows Subsystem for Linux 2 (WSL2) transformed Docker on Windows from a sluggish, compatibility-limited experience into something approaching native Linux performance. Before WSL2, Docker on Windows relied on Hyper-V with a full Linux VM or Windows containers, both with significant overhead. WSL2 provides a real Linux kernel running inside Windows, and Docker takes full advantage of it.
This guide covers the complete setup from WSL2 installation through production-capable Docker development: Docker Desktop versus running Docker directly in WSL2, file system performance optimization, networking, VS Code integration, GPU passthrough for ML workloads, resource limits, and solutions to the most common problems.
WSL2 Installation
On Windows 10 (version 2004+) or Windows 11:
# Open PowerShell as Administrator
# Install WSL2 with Ubuntu (default)
wsl --install
# Or install a specific distribution
wsl --install -d Ubuntu-24.04
# List available distributions
wsl --list --online
# Verify WSL version
wsl --version
# Set WSL2 as default (if not already)
wsl --set-default-version 2
# Check installed distributions
wsl --list --verbose
After installation and reboot, set up your Linux user:
# Inside WSL2 Ubuntu
sudo apt update && sudo apt upgrade -y
# Install essential development tools
sudo apt install -y build-essential curl git wget unzip \
ca-certificates gnupg lsb-release
Docker Desktop vs Docker in WSL2
There are two ways to run Docker on Windows with WSL2. Each has trade-offs:
| Aspect | Docker Desktop | Docker Engine in WSL2 |
|---|---|---|
| Installation | Windows installer, GUI setup | Manual Linux installation in WSL2 |
| License | Free for personal/small business, paid for enterprise (250+ employees) | Free (open source Docker Engine) |
| GUI | Dashboard, settings UI, extensions | CLI only (or Portainer/usulnet) |
| Kubernetes | Built-in single-node cluster | Manual minikube/k3s installation |
| Windows integration | System tray, PATH integration, Windows containers | WSL2 terminal only |
| Resource usage | ~1-2GB RAM overhead | Lower overhead (no desktop process) |
| Updates | Automatic via Docker Desktop | Manual via apt |
Option 1: Docker Desktop with WSL2 Backend
# 1. Download and install Docker Desktop from docker.com
# 2. In Docker Desktop Settings:
# - General > "Use the WSL 2 based engine" (checked)
# - Resources > WSL Integration > Enable for your distro
# 3. Verify from WSL2 terminal
docker version
docker compose version
Option 2: Docker Engine Directly in WSL2
# Inside WSL2 Ubuntu terminal
# Add Docker's official GPG key and repository
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker Engine
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
# Add your user to the docker group
sudo usermod -aG docker $USER
# Start Docker (WSL2 doesn't use systemd by default)
sudo service docker start
# Optional: Auto-start Docker when WSL2 opens
echo 'sudo service docker start' >> ~/.bashrc
[boot]\nsystemd=true to /etc/wsl.conf inside your WSL2 distribution, then restart WSL (wsl --shutdown). With systemd enabled, Docker starts automatically and behaves identically to a standard Linux installation.
File System Performance
This is the single most important performance consideration for Docker on Windows with WSL2. File operations across the Windows-Linux boundary are dramatically slower than native operations.
| File Location | Speed from WSL2 | Speed from Windows |
|---|---|---|
WSL2 filesystem (~/projects) |
Native Linux speed | Slow (9P protocol) |
Windows filesystem (/mnt/c/Users/...) |
Very slow (9P protocol) | Native Windows speed |
/mnt/c/ (the Windows filesystem) when using Docker in WSL2. File operations like npm install, go build, or git status will be 5-10x slower. Always clone repositories inside the WSL2 filesystem: ~/projects/myapp instead of /mnt/c/Users/you/projects/myapp.
# WRONG: Project on Windows filesystem (slow)
cd /mnt/c/Users/you/projects/myapp
docker compose up # Bind mounts will be extremely slow
# CORRECT: Project on WSL2 filesystem (fast)
cd ~/projects/myapp
docker compose up # Near-native Linux performance
# Access WSL2 files from Windows Explorer:
# \\wsl$\Ubuntu\home\you\projects\myapp
For Docker volumes, named volumes always perform well because they are stored within the WSL2 VM's virtual disk. Bind mounts perform well only when mounting from the WSL2 filesystem:
# Good: bind mount from WSL2 filesystem
volumes:
- ./src:/app/src # Fast (WSL2-native)
- pgdata:/var/lib/postgresql/data # Fast (named volume)
# Bad: bind mount from Windows filesystem
volumes:
- /mnt/c/Users/you/src:/app/src # Very slow
Networking
WSL2 networking has evolved through several modes. Understanding the current behavior is essential:
# Default (NAT mode): WSL2 gets its own IP address
hostname -I # Shows WSL2's internal IP
# Ports exposed by Docker are accessible on localhost from Windows
# docker run -p 8080:80 nginx → accessible at http://localhost:8080 from Windows
# WSL2 can access Windows services via the host IP
cat /etc/resolv.conf # nameserver shows the Windows host IP
Mirrored Networking Mode (Windows 11 22H2+)
# %USERPROFILE%/.wslconfig
[wsl2]
networkingMode=mirrored
# Benefits:
# - WSL2 shares Windows network interfaces
# - Same IP address as Windows
# - IPv6 support
# - VPN connectivity works automatically
# - Localhost forwarding is bidirectional
Note: If you use a corporate VPN, mirrored networking mode usually resolves the common issue of WSL2 losing internet connectivity when the VPN is active. This was historically one of the most frustrating WSL2 problems.
VS Code Integration
VS Code with the WSL extension provides the best development experience on Windows with Docker:
# From WSL2 terminal, open a project in VS Code
cd ~/projects/myapp
code .
# VS Code opens with the WSL extension, running the backend inside WSL2
# All file operations, terminal commands, and Docker operations use WSL2
Recommended VS Code extensions for Docker development on WSL2:
- WSL (ms-vscode-remote.remote-wsl) - Required for WSL2 integration
- Dev Containers (ms-vscode-remote.remote-containers) - Development inside Docker containers
- Docker (ms-azuretools.vscode-docker) - Dockerfile and Compose support
The combination of WSL2 + VS Code + Dev Containers gives you a full Linux development environment with a native Windows IDE experience. Your code runs in Linux, your editor runs on Windows, and everything communicates seamlessly.
GPU Passthrough
WSL2 supports GPU passthrough for NVIDIA GPUs, enabling Docker containers to use GPU acceleration for machine learning and compute workloads:
# Prerequisites:
# 1. NVIDIA GPU driver installed on Windows (NOT in WSL2)
# 2. WSL2 with a supported distribution
# Verify GPU access in WSL2
nvidia-smi
# Install NVIDIA Container Toolkit in WSL2
distribution=$(. /etc/os-release; echo $ID$VERSION_ID)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \
sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt update
sudo apt install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
# Test GPU in Docker
docker run --rm --gpus all nvidia/cuda:12.3.0-base-ubuntu22.04 nvidia-smi
# docker-compose.yml with GPU access
services:
ml-training:
image: pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
volumes:
- ./models:/workspace/models
- ./data:/workspace/data
Resource Limits
By default, WSL2 can consume up to 50% of system memory (or 8GB, whichever is less on older versions). For Docker workloads, you need to configure these limits explicitly:
# %USERPROFILE%/.wslconfig (Windows side)
[wsl2]
memory=8GB # Limit WSL2 memory
processors=4 # Limit CPU cores
swap=4GB # Swap file size
swapFile=C:\\temp\\wsl-swap.vhdx
localhostForwarding=true
networkingMode=mirrored
# Reclaim memory from WSL2 (useful for long-running sessions)
[experimental]
autoMemoryReclaim=gradual # dropcache | gradual | disabled
sparseVhd=true # Automatically shrink the virtual disk
# Apply changes (from PowerShell)
wsl --shutdown
wsl
autoMemoryReclaim=gradual and sparseVhd=true in the [experimental] section. Without these, WSL2 progressively consumes memory and disk space that it never releases back to Windows, even after containers are stopped. These settings are stable despite being labeled "experimental."
Troubleshooting
Common Issues and Solutions
| Problem | Cause | Solution |
|---|---|---|
| Docker commands hang | Docker daemon not running | sudo service docker start or restart Docker Desktop |
| Very slow file I/O | Project on /mnt/c/ |
Move project to ~/projects/ |
| No internet in WSL2 | VPN or DNS issues | Use mirrored networking or fix /etc/resolv.conf |
| Port already in use | Windows service on same port | Stop Windows service or use different port |
| WSL2 consuming all RAM | No memory limit set | Set memory in .wslconfig |
| Disk space growing | Virtual disk does not shrink | Enable sparseVhd=true or compact manually |
| Permission denied on bind mount | UID mismatch | Add user: "1000:1000" to Compose service |
# Fix DNS issues (common with VPNs)
# In WSL2:
sudo rm /etc/resolv.conf
sudo bash -c 'echo "nameserver 8.8.8.8" > /etc/resolv.conf'
sudo bash -c 'echo "[network]\ngenerateResolvConf = false" > /etc/wsl.conf'
# Compact the WSL2 virtual disk (reclaim space)
# From PowerShell (WSL must be shut down):
wsl --shutdown
diskpart
# select vdisk file="C:\Users\you\AppData\Local\Packages\...\ext4.vhdx"
# attach vdisk readonly
# compact vdisk
# detach vdisk
Tips for Daily Use
- Always work from the WSL2 filesystem. Clone repos to
~/projects/, not/mnt/c/. - Use Windows Terminal for the best WSL2 terminal experience. It supports tabs, profiles per distribution, and GPU-accelerated rendering.
- Set up SSH keys in WSL2, not on Windows. Git operations should run from WSL2.
- Use
wsl --shutdownwhen done for the day. WSL2 does not release memory gracefully otherwise. - Pin Docker Compose files to version 2 syntax (no
version:key needed). Both Docker Desktop and Docker Engine support it. - Use named volumes for databases. Never bind-mount database data directories across the Windows-Linux boundary.
For managing Docker containers and infrastructure running in WSL2, tools like usulnet provide a web-based interface that is accessible from your Windows browser, giving you visibility into container status, logs, and resource usage without switching between Windows and WSL2 terminals.