Introduction
Docker Desktop provides many features, but it is also heavy and slow to start. This made me remove it and install only Docker Engine directly in WSL, reducing startup time from minutes to seconds.
In this post I’ll show how to do it, some configuration tips, how to share the Docker socket across WSL distros (like Rancher Desktop and Podman Desktop do automatically), and a comparison with those alternatives.
Filesystem performance warning: bind-mounting source code from
/mnt/c(Windows drives) into containers is roughly 10Γ slower than using the WSL ext4 filesystem at~/. The 9P protocol crossing the VM boundary is the bottleneck. Keep your repos under~/inside WSL and access them from Windows via\\wsl.localhost\Ubuntu\home\you\repo.
Terminology
- Docker Engine: The core component of Docker, providing orchestration of the container runtime, image management, networking, and storage capabilities.
- Docker CLI: The command-line interface to interact with the Docker API.
Why do this?
The main motivations for running Docker Engine directly in WSL instead of Docker Desktop, Rancher Desktop, or Podman:
- License cost: Docker Desktop requires a paid subscription for companies with 250+ employees or $10M+ annual revenue. Docker Engine is Apache 2.0 β free for any org size. Rancher Desktop and Podman Desktop are also free, but have architectural trade-offs (see comparison).
- Startup time: Docker Desktop can take minutes to start. Docker Engine in WSL starts in ~14 seconds with systemd, or near-instantly with a profile script.
- Resource usage: no GUI, no extra VM layer, no bundled Kubernetes. Just dockerd running as a systemd service (~100MB RAM, ~1-3% CPU idle). Rancher Desktop’s k3s alone uses ~500MB idle.
- Full control: you manage Docker like a Linux server β systemd, daemon.json, config files. No GUI abstraction layer hiding what is happening.
- Native Docker compatibility: 100% compatible socket, Compose, Swarm, BuildKit. No emulation layer, no SSH proxy, no
podman-composeedge cases. - Familiarity: if your production servers run Docker Engine on Linux, your dev environment matches.
When this approach is best
- You are CLI-first and prefer terminal workflows over GUIs.
- Your company needs to avoid Docker Desktop licensing costs.
- You use Docker Compose extensively, including
network_mode: hostor port mappings below 1024. - You rely on Docker Swarm.
- You want minimal resource overhead on your dev machine.
- You already work inside WSL for development and want the container daemon in the same distro.
When to pick something else
See the full comparison later in this post, but in short:
- Rancher Desktop if you need a GUI and one-click Kubernetes.
- Podman Desktop if your security policy requires rootless containers.
- Docker Desktop if you need native Windows containers or already have a license.
Installing Docker Engine on WSL
To install Docker Engine in WSL, first follow the official docs for your distro. I use Ubuntu, so I’ll give instructions for it in this post. For other distros, click here.
We need to add the docker repository to apt’s sources and install its components (Docker Engine, Docker CLI, Containerd, BuildX and Docker Compose):
| |
To use the docker CLI command without root permissions, create a docker group and add your user to it:
| |
The
newgrp dockercommand starts a new shell with the group membership applied. Log out and back in for a permanent effect.
Then start the Docker service:
| |
If you enabled systemd in WSL (see below), use
sudo systemctl start dockerinstead.
And test with the hello-world image:
docker run hello-world
How to auto-start Docker when WSL starts
There are two approaches:
With systemd (recommended)
WSL 0.67.6+ supports systemd β Docker starts automatically as a systemd service.
In your WSL distro, edit /etc/wsl.conf (create it if it doesn’t exist) and add:
| |
Then, from Windows (PowerShell or CMD), shut down WSL:
| |
Reopen your WSL shell and run docker ps to confirm Docker is running.
Note: systemd adds a small startup delay (~14 seconds on my machine). If this is a concern, use the profile script approach below.
With a profile script (alternative, no systemd)
If systemd is not available or you want to avoid its startup latency, add the following script to your shell profile (.zshrc, .bashrc, etc.):
| |
This checks if you’re inside WSL (in case you share your profile across machines) and starts Docker if it’s not running.
Source: https://github.com/nickjj/dotfiles/commit/badd3265e5c8f6eca90d3b57df29292545332500
How to access Docker from Windows
To access the Docker socket from outside WSL, configure the Docker daemon to listen on a TCP port.
If using systemd
Do NOT edit /lib/systemd/system/docker.service directly β changes are lost on package updates. Instead, create a systemd override:
| |
Add the following, which clears the default ExecStart and adds a TCP listener:
| |
Then reload systemd and restart Docker:
| |
Security note:
tcp://0.0.0.0:2375exposes the Docker API without authentication on all interfaces. WSL2 uses a NAT network, which limits exposure, but consider using a firewall or TLS if you are on an untrusted network.
If starting with a profile script (no systemd)
Edit /etc/docker/daemon.json and add the hosts configuration:
| |
Then restart Docker:
| |
When you set
hostsindaemon.json, the default Unix socket is not used unless you explicitly include it (as shown above).
After either setup, configure the DOCKER_HOST environment variable on Windows:
| |
(or tcp://[::1]:2375 for IPv6).
With DOCKER_HOST set, tools like Testcontainers and Localstack will use the Docker engine running inside WSL.
π‘ I wrote posts about Testcontainers and Localstack.
Access from the command line
I prefer running commands from the WSL shell, but there are alternatives:
- Install Docker CLI on Windows (it respects
DOCKER_HOST). - Run commands via
wsl:wsl -d Ubuntu docker ps
Comparison with alternatives
This section compares Docker Engine in WSL with Rancher Desktop and Podman Desktop β the two main free alternatives that also avoid Docker Desktop’s license cost.
Rancher Desktop
| Docker Engine in WSL | Rancher Desktop |
|---|---|
| Apache 2.0, free for all org sizes | Apache 2.0, free for all org sizes |
| No GUI β CLI-only | GUI dashboard for containers, images, volumes |
| No Kubernetes bundled (DIY with kind/k3d/minikube) | Bundled k3s with version dropdown |
| Lower resource usage (~14s startup with systemd, ~100MB daemon) | Higher resource usage (k3s idle ~500MB RAM, ~3.8GB disk base) |
| Manual config (systemd, daemon.json, profiles) | GUI-driven config, abstraction layer over dockerd/nerdctl |
| Native Docker socket, Compose, Swarm β 100% compatible | Docker-compatible via dockerd or nerdctl |
Rancher Desktop is the closest like-for-like Docker Desktop replacement. It is the right choice if you want a GUI and one-click Kubernetes without paying for Docker Desktop. The trade-off is higher resource usage and less control over the daemon β you manage Docker through Rancher’s GUI rather than directly.
Podman Desktop
| Docker Engine in WSL | Podman Desktop |
|---|---|
Daemon-based (dockerd runs as root) | Daemonless, rootless by default β containers run as your user |
| Docker Compose 100% compatible | podman-compose has gaps: network_mode: host behaves differently in rootless mode, bind-mount path translation can break on WSL (resolves to D:\ paths instead of WSL paths) |
| Docker Swarm supported | Docker Swarm not supported |
| BuildKit native β warm builds ~6s | Buildah-based β warm builds ~8s, layer caching differences |
Ports < 1024 (-p 80:80) work normally | Ports < 1024 require extra capabilities in rootless mode |
| Daemon always running (background CPU ~1-3%) | No persistent daemon β zero resource usage when idle |
Podman’s daemonless, rootless architecture is a genuine security improvement β a container breakout gives the attacker your user permissions, not root. However, the compatibility friction with Docker tooling is real. Most workflows work with alias docker=podman, but edge cases surface in Docker Compose, port mapping, and tools that hardcode /var/run/docker.sock.
Choose Podman if security requirements mandate rootless containers or your team is aligned with Red Hat / OpenShift tooling. Choose Docker Engine in WSL if you want everything to “just work” without configuration workarounds.
Possible errors
Invalid credsStore configuration
If Docker Desktop was previously installed, the credentials store may be set to docker-credential-desktop, causing this error:
| |
Fix: edit ~/.docker/config.json and remove the credsStore property.
Duplicate hosts configuration
If you set up Docker with systemd and get the following error on startup, it is likely due to duplicate host configuration:
| |
The systemd service file passes hosts via -H flags, but you also set hosts in /etc/docker/daemon.json. Docker rejects the duplicate configuration.
To fix: remove the hosts key from /etc/docker/daemon.json and configure the TCP listener via the systemd override as shown above.
What is 9P and why does it matter?
9P (Plan 9 Filesystem Protocol) is a network filesystem protocol from Bell Labs. WSL2 uses it to share Windows drives (NTFS) into the Linux VM. When you access C:\ from inside WSL at /mnt/c, every file operation travels over 9P:
| |
Every stat(), readdir(), open(), close() serializes and round-trips across the VM boundary. For workloads touching thousands of small files β npm install, git status, find, Vite/webpack file watchers β this compounds into a ~10Γ slowdown compared to native ext4 inside WSL.
Inside ~/ (the WSL home directory), files live on an ext4 filesystem in a VHDX file β the same kernel, same driver, no protocol translation overhead. The difference is not about Docker or WSL version; it is about whether the filesystem path crosses the 9P boundary.
The fix is always the same regardless of which container tool you use: store source code under ~/ inside WSL (not on /mnt/c/), bind-mount from Linux paths into containers, and access files from Windows tools via the \\wsl.localhost\Ubuntu\home\you\repo network path.
What about the \\wsl.localhost\ network share?
Windows exposes every WSL distro’s filesystem as a hidden SMB share at \\wsl.localhost\Ubuntu\, \\wsl.localhost\Debian\, etc. You can even map it to a drive letter:
| |
This is Windows accessing Linux files over the network β it goes through the Windows networking stack, not through 9P. This is the recommended way to edit WSL files from Windows applications (VS Code, Explorer, etc.) without crossing the slow 9P path.
Crucially: this does NOT affect Docker bind-mount performance. Docker inside WSL bind-mounts Linux paths using the Linux kernel. If your repo is at ~/my-project, you mount it as -v ~/my-project:/app β Linux ext4 to Linux overlayfs, no 9P involvement. The Windows SMB path is only for editing, not for container mounts.
Older blog posts might recommend the reverse β storing code on Windows and accessing it via /mnt/c β but that pattern is the slow path. Keep code in WSL, edit from Windows via the \\wsl.localhost\ share.
