Setup: Docker Desktop on Windows 11 with WSL2
Why this matters
Everything in this track runs through Docker Desktop's WSL2 backend — if
it's not set up correctly, every later exercise fails in confusing ways
(wrong docker binary, containers that can't see your files, or
containers that only half-work). Get this right once and you won't think
about it again.
Concepts
Docker needs a Linux kernel
Docker on Linux runs containers directly against the host's Linux kernel. Windows doesn't have a Linux kernel, so Docker can't run natively on it. That single fact explains the whole setup you're about to do: something has to provide a Linux kernel for Docker to talk to.
Docker Desktop supplies that kernel through WSL2
Docker Desktop solves the missing-kernel problem by running a real,
lightweight Linux virtual machine (managed by WSL2) and running the
Docker engine (the dockerd daemon) inside it. That VM is where your
containers actually execute.
In Docker Desktop: the small whale icon in your Windows system tray shows the engine's state. When it's steady (not animating), the engine inside that VM is running. The Docker Desktop dashboard window is just a GUI client talking to that same engine.
Your Ubuntu terminal is a client, not the engine
The WSL2 Ubuntu distro you used in the Linux track is separate from that
Docker VM. When you type docker inside Ubuntu, you're running a client
that talks to the daemon in the neighboring VM over a socket. Nothing
Docker-related is actually installed in Ubuntu; the CLI is injected and
pointed at the shared engine.
One engine, many clients
Docker Desktop's WSL2 backend shares one single Docker engine across
every WSL2 distro you enable integration for, and with Windows itself.
Containers you start from Ubuntu and containers you start from PowerShell
are the same containers, visible in the same docker ps and in the same
Docker Desktop window.
┌──────────────────────── Windows 11 Host ─────────────────────────┐
│ │
│ ┌──────────┐ ┌──────────────┐ ┌────────────────────────┐ │
│ │ Ubuntu │ │ PowerShell │ │ Docker Desktop GUI │ │
│ │ docker │ │ docker │ │ (dashboard client) │ │
│ └────┬─────┘ └──────┬───────┘ └───────────┬────────────┘ │
│ │ clients │ │ │
│ └────────────────┴───────────┬───────────┘ │
│ │ Docker socket │
│ ┌────────────▼───────────────┐ │
│ │ WSL2 utility VM │ │
│ │ ┌───────────────────────┐ │ │
│ │ │ dockerd (the engine) │ │ │
│ │ │ + Linux kernel │ │ │
│ │ └───────────────────────┘ │ │
│ └─────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
One engine, many clients — all see the same containers/images
In Docker Desktop: the Containers tab lists every container regardless of which shell started it. Start something from Ubuntu and it appears in that list immediately — proof there's one engine, not one per shell.
The filesystem boundary matters for speed
Files inside your Ubuntu home directory (/home/you/...) live on the
Linux side and are fast to bind-mount into containers. Files under
/mnt/c/... (your Windows C: drive seen from WSL2) cross a translation
boundary and are noticeably slower for heavy I/O. Keep project files in
your Linux home directory for anything performance-sensitive.
Linux side (fast) Windows side (slow across boundary)
┌─────────────────────┐ ╎ ┌──────────────────────────┐
│ /home/you/project │ native ╎ 9P │ C:\Users\you\project │
│ → bind-mount ──────────────► ╎ ◄────── │ seen as /mnt/c/... in │
│ into container │ fast ╎ xlate │ WSL2 (per-file overhead)│
└─────────────────────┘ ╎ └──────────────────────────┘
translation boundary
Integration is per-distro; resources are global
WSL2 integration is a per-distro toggle: enabling it for "Ubuntu" injects
the docker CLI into that distro and points it at the shared engine.
Resource limits (CPU, memory, disk) apply to the whole Docker Desktop VM
and are set once in Docker Desktop's Settings, not per distro.
Command reference
| Command | What it does | Example |
|---|---|---|
docker version |
Shows client and server (engine) version info; confirms the CLI can reach the daemon | docker version |
docker info |
Shows engine-wide details: storage driver, number of containers/images, resource limits | docker info |
docker run hello-world |
Pulls and runs a minimal test image that prints a confirmation message, then exits | docker run hello-world |
docker system df |
Shows disk space used by images, containers, volumes, and build cache | docker system df |
docker ps -a |
Lists all containers, running or stopped | docker ps -a |
wsl --list --verbose |
(PowerShell) Lists installed WSL distros and their WSL version | wsl --list --verbose |
wsl --status |
(PowerShell) Shows the default WSL distro and default WSL version | wsl --status |
Flag breakdown for the multi-part commands above:
docker ps -a:docker pson its own lists only running containers.-a(short for--all) adds stopped/exited containers to the list, so you can see things that already finished.
wsl --list --verbose:--listasks WSL to enumerate installed distros.--verboseadds columns for each distro's state (Running/Stopped) and WSL version (1 or 2) — you want version2.
In Docker Desktop:
docker system dfhas a visual equivalent under the whale menu — open the dashboard, and the disk-usage breakdown for images, containers, volumes, and build cache appears (some versions put it behind a "Disk usage" or "Clean up" view). The Images tab is the GUI equivalent ofdocker images, and the Containers tab is the GUI equivalent ofdocker ps -a.
Hands-on exercises
(PowerShell) Confirm WSL2 and your Ubuntu distro are present:
wsl --list --verboseExpect to see
Ubuntulisted withVERSION=2. If it's missing, go finish the setup step in the Linux track's prerequisites (wsl --install -d Ubuntu) before continuing here.(Windows) Install Docker Desktop from
https://www.docker.com/products/docker-desktop/if it isn't already installed. During setup, accept the default "Use WSL 2 instead of Hyper-V" option when prompted (this is the default on Windows 11 and is what this whole track assumes).(Docker Desktop GUI) Open Docker Desktop. Go to Settings → General and confirm "Use the WSL 2 based engine" is checked. Go to Settings → Resources → WSL Integration and enable the toggle next to Ubuntu (or whatever you named your distro). Click Apply & Restart. This is the one setup step that has no CLI equivalent — the integration toggle is what makes the CLI work at all in the next step.
(WSL2 Ubuntu terminal) Open your Ubuntu terminal and confirm the CLI is wired up:
docker versionExpect a
Client:block and aServer:block both printing version numbers with no connection errors. If you only see theClient:block followed by a "Cannot connect to the Docker daemon" error, the WSL integration toggle from step 3 isn't applied yet, or Docker Desktop isn't running.(WSL2 Ubuntu terminal) Run the classic test container:
docker run hello-worldExpect output that includes
Hello from Docker!and a short explanation of the steps Docker just took (pulled the image, created a container from it, ran it, streamed its output, then it exited).(Docker Desktop GUI, then CLI) Now find that same container in the GUI. Open Docker Desktop and click the Containers tab. You'll see one entry created from
hello-worldwith anExitedstatus. Click into it and open its Logs sub-tab — you'll see the exact sameHello from Docker!text the terminal printed. Then cross-check from the CLI:docker ps -a docker logs $(docker ps -aq --filter ancestor=hello-world | head -n1)Expect the CLI to show the same container and the same logs. This is the point of the whole module: the GUI and the CLI are two views of one engine's state, not two separate systems.
(Docker Desktop GUI) Go to Settings → Resources → Advanced (on some versions this is just Settings → Resources) and note the CPU limit, memory limit, and disk image size sliders. Set memory to at least 4 GB if it's lower (containers you build later in this track are small, but comfortable headroom avoids confusing OOM-kills). Click Apply & Restart if you change anything.
(WSL2 Ubuntu terminal) Check where Docker thinks your project files should live, performance-wise. Run:
pwdIf it prints something starting with
/mnt/c/..., you're working on the Windows filesystem from inside Linux — slower for Docker builds with lots of small files. Create a project home on the Linux side instead:mkdir -p ~/learn-docker cd ~/learn-docker pwdExpect a path like
/home/<you>/learn-docker— use this as your working directory for hands-on exercises in later modules that build images from local files.(WSL2 Ubuntu terminal) Clean up the test container so
docker ps -astays tidy going forward:docker rm $(docker ps -aq --filter ancestor=hello-world)Flag breakdown:
docker ps -aqlists all containers (-a) as bare IDs only (-q, for "quiet").--filter ancestor=hello-worldnarrows that list to containers created from thehello-worldimage.- The
$( ... )wrapper feeds those IDs todocker rm, which deletes them. Confirm they're gone withdocker ps -a, or refresh the Containers tab in Docker Desktop and watch the entry disappear.
Diagnose and fix. In Docker Desktop, go to Settings → Resources → WSL Integration and turn the Ubuntu toggle off, then Apply & Restart. Back in your Ubuntu terminal, run:
docker versionRead the exact error (you'll get the
Client:block, then a "Cannot connect to the Docker daemon" message — the client is fine, the engine is unreachable from this distro). Then re-enable the toggle, apply, and confirmdocker versionworks again. This is the single most common "Docker suddenly stopped working" cause on Windows — knowing what the broken state looks like saves you a confused half hour later.
Independent challenge
No commands given here — figure it out yourself using what you know from this module and earlier ones.
Task: The module claims there is one single Docker engine shared across every WSL2 distro and Windows itself, not a separate engine per shell. Prove it to yourself empirically without trusting the claim. Start a long-running container from one shell (your PowerShell prompt), then, from a different shell (your WSL2 Ubuntu terminal), confirm you can see that exact container, read the same status, and stop it from there — and confirm the change is reflected back in Docker Desktop's Containers tab. Lean on the shell fluency from the Linux track (running a background process and referring to it later by a stable name) to keep the container alive long enough to inspect it from the other side.
Stuck? One hint
Give the container a fixed --name and a command that keeps it alive (something that sleeps) when you start it detached in one shell, then list and stop it by that name from the other shell.
Common mistakes & troubleshooting
- "Cannot connect to the Docker daemon" in WSL2 but Docker Desktop looks "Running." The per-distro WSL integration toggle is off, or Docker Desktop was restarted and the integration hasn't re-attached yet — reopen Docker Desktop Settings → Resources → WSL Integration and check the toggle.
- Docker Desktop won't start, complains about virtualization.
Hardware virtualization (Intel VT-x/AMD-V) must be enabled in the
BIOS/UEFI, and Windows features "Virtual Machine Platform" and
"Windows Subsystem for Linux" must be enabled —
wsl --installon a fresh machine normally handles this, but a re-imaged or corporate laptop sometimes has virtualization disabled at the firmware level. - Builds and file operations feel slow. You're almost certainly
working out of
/mnt/c/.... Move the project into your Linux home directory (~/...). - Using both PowerShell's
dockerand WSL2'sdockerand getting confused about what's running. They're the same engine, sodocker psgives identical results in both — if it doesn't, you have Docker installed twice (e.g. a stray non-Desktop Docker CLI in PowerShell) and should remove the duplicate. docker run hello-worldhangs on "Pulling from library/hello-world." Usually a network/proxy issue — check Docker Desktop's Settings → Resources → Proxies if you're on a corporate network, or check your internet connection.
Checkpoint quiz
Write down your answer to each question before expanding it — checking without attempting first is the single easiest way to fool yourself into thinking you've learned this.
Show questions
- Where does the actual Docker engine (
dockerd) run when you use Docker Desktop's WSL2 backend on Windows? - What does enabling "WSL Integration" for a specific distro actually do?
- Why is a project stored under
/home/you/...in WSL2 generally faster for Docker builds than one under/mnt/c/...? - If
docker run hello-worldin PowerShell works but the same command in your Ubuntu WSL2 terminal fails with a daemon connection error, what's the first thing to check? - What command shows both the Docker client version and the engine (server) version at once?
- True or false: each WSL2 distro with integration enabled gets its own separate Docker engine and separate set of images/containers.
- Which Docker Desktop tab shows the same containers
docker ps -alists, and where in that tab do you read a container's logs?
Show answers
- Inside a lightweight Linux VM that Docker Desktop manages via WSL2 — not directly inside your Ubuntu distro, and not as a native Windows process.
- It exposes the
dockerCLI inside that distro and points it at the shared Docker engine socket, so commands run from that distro's shell reach the same engine Docker Desktop manages. /mnt/c/...paths cross the WSL2 filesystem translation boundary between Windows and Linux, which adds overhead per file operation;/home/you/...is native to the Linux VM's filesystem, so it's much faster for workloads that touch many files (likedocker buildcopying a large context).- Whether Docker Desktop's WSL Integration toggle is enabled for that specific distro (Settings → Resources → WSL Integration), and whether Docker Desktop itself is running.
docker version.- False — all distros with integration enabled, plus Windows itself, share the same single Docker engine and the same containers/images.
- The Containers tab. Click a container to open its detail view,
then select the Logs sub-tab to read the same output
docker logswould print.
Further reading & sources
- Docker Desktop WSL 2 backend on Windows - official guide to how Docker Desktop uses the WSL2 engine and how to enable per-distro integration.
- Install Docker Desktop on Windows - the canonical install steps and system requirements this module walks through.
- Microsoft: What is WSL 2? - background on the lightweight utility VM and real Linux kernel that Docker's engine runs inside.
- Microsoft: Comparing WSL versions and file-system performance - explains the
/mnt/ccross-OS translation cost behind this module's "keep files on the Linux side" advice. - docker version / docker info reference - the CLI reference for the client/server verification commands used here.
Next
Continue to 01-containers-vs-vms-concepts to understand what a container actually is under the hood before you start running them in anger.