Two ways to run containers on Windows without Docker Desktop, plus every red error message I met on the way.
Here is the situation. Your laptop is locked down, Docker Desktop is not on the approved list (or the license is a "talk to finance" thing), and you still need to run docker build before lunch. Good news: you do not need Docker Desktop. Bad news: the two replacements each have their own little traps, and one of them quietly hands you root.
TL;DR
-
Option 1: WSL2 + Docker Engine. Install Ubuntu in WSL,
apt install docker.io, done. Feels exactly like Docker. Watch out for systemd and thedockergroup. -
Option 2: Podman. No daemon, rootless by default, same commands (
alias docker=podman). Watch out for low ports and the Docker named pipe. -
Security gotcha: "fixing sudo" by joining the
dockergroup gives you root-level power. Rootless Podman and real secrets beat that.
Table of contents
- 1. Pick your path
- 2. Option 1 WSL2 and Docker Engine
- 3. Option 2 Podman on Windows
- 4. The errors I hit and how to fix them
- 5. Security gotcha
- 6. Checklist
- Sources
1. Pick your path
Both options use WSL2 under the hood. The difference is who runs the containers.
| WSL2 + Docker Engine | Podman | |
|---|---|---|
| Where it runs | Docker daemon inside your Ubuntu distro | A small Podman machine (a WSL2 distro) |
| Daemon | Yes, dockerd running as root |
No central daemon |
| Default user | root daemon, you call it via sudo or the docker group |
Rootless |
| Commands | docker ... |
podman ... (almost the same) |
| You run it from | The Ubuntu shell | PowerShell, CMD or WSL |
| Best for | "I want my Docker workflow back" | Locked-down or security-minded setups |
My rule of thumb: if your team's scripts are full of docker compose and Docker-only flags, go with Option 1. If you mostly build and run images and your security team frowns at root daemons, go with Podman.
2. Option 1 WSL2 and Docker Engine
This keeps your workflow almost identical to Docker Desktop. You just do the plumbing yourself.
Step 1. Install WSL. Open PowerShell as administrator:
wsl --install
Restart the machine. You need Windows 10 version 2004 (build 19041) or newer, or Windows 11. By default this installs Ubuntu and sets new installs to WSL 2. Check it:
wsl -l -v
You want to see VERSION 2 next to your distro.
Step 2. Install (or pick) a distro. Ubuntu is the default, but you can ask for it by name, and list the others:
wsl --install -d Ubuntu
wsl --list --online
Step 3. Start Ubuntu.
wsl -d Ubuntu
Step 4. Install Docker Engine inside WSL (no Desktop):
sudo apt update
sudo apt install -y docker.io
sudo systemctl enable docker
sudo systemctl start docker
Step 5. Test.
sudo docker run hello-world
If you see "Hello from Docker!", you have a working engine. Notice the sudo though. We will come back to that, because that is where people (me included) take a shortcut with a security price tag.
Pros: same commands, same Dockerfiles, and an easy path to Minikube later. Cons: you manage the engine yourself, and it is a little less smooth than Desktop.
Source: Install WSL
3. Option 2 Podman on Windows
Podman is daemonless: there is no big root process waiting for orders. On Windows, Podman runs inside a "Podman machine", which is a WSL2 distro it manages for you. The podman command in PowerShell talks to it.
Step 1. Install Podman. The official way is the Windows installer from the Podman GitHub releases page. The quick way is winget:
winget install RedHat.Podman
Close and reopen the terminal so podman.exe is on your PATH.
Step 2. Create and start the Podman machine.
podman machine init
podman machine start
If WSL is not installed yet, podman machine init offers to install it for you, reboots, and carries on after you log in. Nice, but do not be surprised by the reboot.
Step 3. Test.
podman run hello-world
If a short image name like hello-world makes Podman ask which registry you mean, give it the full name: docker.io/library/hello-world.
Step 4. Use your Docker muscle memory.
podman build -t myapp .
podman run myapp
alias docker=podman # in a bash shell
In PowerShell the equivalent is Set-Alias docker podman (add it to your profile to keep it).
Pros: no daemon, rootless by default, Docker-compatible CLI, and security teams like it. Cons: some edge cases with networking and volumes, and Kubernetes integration is a bit more work.
Source: Podman installation, Podman for Windows guide
4. The errors I hit and how to fix them
The fun part. Here is what broke, why, and the fix.
Error 1. wsl --install just prints the help text.
Why: WSL is already installed, so the "install everything" path does not run.
Fix: list what is available and install a distro by name:
wsl --list --online
wsl --install -d Ubuntu
If the download hangs at 0.0%, add --web-download: wsl --install --web-download -d Ubuntu.
Error 2. System has not been booted with systemd as init system (PID 1). Can't operate.
Why: systemctl needs systemd, and some distros or older WSL builds still use the old WSL init. The current Ubuntu from wsl --install has systemd on by default, so you mostly hit this with other distros.
Fix: check your WSL version is 0.67.6 or newer, then add this to /etc/wsl.conf inside the distro:
[boot]
systemd=true
Then restart WSL (see Error 4). Confirm with systemctl status.
Error 3. permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
Why: the Docker socket belongs to root and the docker group. Your user is in neither, so plain docker ps gets the door slammed.
Fix: add yourself to the group, then restart WSL so the new group sticks:
sudo usermod -aG docker $USER
wsl --shutdown
wsl -d Ubuntu
Then docker ps works without sudo. Here is my lab after the fix: empty table, no error, no sudo.
Before you celebrate, read section 5. This fix has a catch.
Error 4. I changed a setting and nothing happened.
Why: WSL only picks up wsl.conf changes after the distro fully stops. Microsoft calls it the "8 second rule": close every shell, then wait about 8 seconds.
Fix: wsl --list --running should say there are no running distributions. Or use the big hammer: wsl --shutdown. It stops all distros, including your Podman machine, so save your work first.
Error 5. Rootless Podman will not bind port 80.
Why: rootless containers cannot bind ports below 1024.
Fix: map to a higher host port (-p 8080:80), which is what I do on a dev laptop anyway. Only if you really need port 80, stop the machine and switch it to rootful:
podman machine stop
podman machine set --rootful
podman machine start
podman machine set --rootful=false takes you back.
Error 6. Docker tools talk to the wrong engine.
Why: Podman listens on the same named pipe Docker clients use by default, npipe:////./pipe/docker_engine. If something else already holds that pipe, the two fight.
Fix: point your tools at Podman's own pipe by setting DOCKER_HOST to npipe:////./pipe/podman-machine-default.
Error 7. podman machine init fails after the automatic WSL install.
Why: the auto-install sometimes gets stuck halfway.
Fix: install WSL manually with wsl --install from an admin PowerShell, reboot, and run podman machine init again. Also check that virtualization is enabled: WSL needs it, and a VM host needs nested virtualization.
Source: WSL config, 8 second rule, systemd in WSL
5. Security gotcha
This is the DevSecOps bit. Read it before you copy any of the fixes above to a shared machine.
-
The
dockergroup is root with extra steps. Docker's own docs say it plainly: thedockergroup grants root-level privileges. Anyone in it can start a container that mounts/from the host and edit anything. On your personal lab laptop, fine. On a shared build box, think twice before runningusermod -aG dockerfor everyone. -
Rootless is the safer default, so keep it. Podman machines start rootless.
--rootfulexists, but switching to it just to get port 80 trades a small annoyance for a bigger blast radius. Map to 8080 instead. -
Your Windows drives are inside WSL. By default WSL mounts
C:\at/mnt/c. A container started with-v /mnt/c:/hostcan read your whole user profile, browser data included. Mount only the project folder you need. -
Secrets in compose files end up in Git.
environment: DB_PASSWORD=SuperSecret123in a compose file is how passwords travel to a public repo. Keep secrets out of the file: use a.envfile listed in.gitignore, or Podman secrets, which are mounted as a file and never baked into an image:
printf 'my-lab-password' | podman secret create db_password -
podman run --secret db_password docker.io/library/alpine cat /run/secrets/db_password
- License is a security-adjacent problem too. Docker Desktop is free for personal use and small businesses (under 250 employees and under $10M revenue). Bigger companies need a paid plan. Unlicensed tools on corporate laptops tend to get noticed by the same people who run your security reviews.
Source: Docker post-install steps (docker group warning), podman secret create
6. Checklist
- [ ]
wsl -l -vshows my distro onVERSION 2 - [ ]
systemctl statusworks inside Ubuntu (systemd on) - [ ]
docker run hello-worldorpodman run hello-worldprints its hello - [ ] I know the
dockergroup equals root, and I only use it on my own lab machine - [ ] My Podman machine is still rootless, and I map ports above 1023
- [ ] I mount project folders, not the whole
/mnt/c - [ ] No passwords in compose files:
.envis in.gitignoreor I usepodman secret - [ ] I run
wsl --shutdownknowing it stops every distro, Podman included
Which did you pick on your Windows machine, WSL2 + Docker or Podman? And what was your first red error? Tell me below.
Sources
- Microsoft Learn: Install WSL
- Microsoft Learn: Use systemd to manage Linux services with WSL
- Microsoft Learn: Advanced settings configuration in WSL
- Podman docs: Installation
- Podman for Windows guide
- Podman docs: podman machine set
- Podman docs: podman secret create
- Docker docs: Linux post-installation steps
- Docker docs: Docker Desktop license agreement


Top comments (0)