Rootless Podman in WSL: cgroup v2, Storage, and systemd
Run Linux-native Podman as a non-root user inside WSL, verify subordinate IDs and cgroup v2, and use systemd without mistaking WSL for a server.
Podman can run directly inside a WSL Linux distribution, but that is a different arrangement from Podman Desktop’s Windows-managed machine. In the Linux-native arrangement, the distro’s kernel, user namespaces, cgroups, storage filesystem, and systemd manager determine what the container runtime can do. Installing the podman command is only the first step. A useful production-style setup verifies rootless ID mapping, cgroup mode, storage placement, and lifecycle behavior before building automation on top of it.
This article is about a development or CI-oriented Podman engine running inside a WSL 2 distro. It does not claim that WSL is equivalent to a dedicated container host or that a Podman container survives when WSL stops. If you use Podman Desktop or podman machine on Windows, follow that product’s machine lifecycle and remote-client model instead of mixing its engine with the one described here.
Confirm which Podman engine you are using
On Windows, the Podman CLI can be a remote client that talks to a Podman service in a WSL-backed machine. When the Linux package is installed inside a user distro, the Linux CLI normally talks to a local engine using that distro’s kernel. Do not infer the endpoint from the command name. In PowerShell, inspect podman system connection list; inside WSL, inspect command -v podman, podman version, and podman info --debug. The command context, storage path, and reported remote/local mode should agree with the engine you intend to operate.
Install Podman from the distro’s supported package repository and record the packaged version. Package freshness and included subcommands vary by distribution, so avoid adding a second repository or copying instructions for a different release before checking the supported package set. If the distro provides Quadlet separately, install that package as documented by its maintainer. Enable systemd in WSL only when the distribution and WSL version support it; the systemd manager operates inside an already-running WSL environment.
Run the Linux-native CLI as your everyday Linux user, without sudo, and inspect the host data:
podman version
podman info --debug
The output should identify the engine’s graph root, storage driver, cgroup version/manager, network backend, and whether it is rootless. Preserve the output alongside the distro version and uname -r. A command run as root may report a different storage graph and show a different set of containers; Podman’s rootless and rootful stores are intentionally separate views.
Rootless mode depends on user-namespace prerequisites
Rootless Podman creates a user namespace so a process that is unprivileged in the distro can appear as a different user inside a container. The mapping normally uses subordinate ID ranges recorded in /etc/subuid and /etc/subgid, along with the newuidmap and newgidmap helpers. Check the exact account entry and binaries before troubleshooting image ownership:
id
grep "^$(id -un):" /etc/subuid /etc/subgid
command -v newuidmap newgidmap
podman info --debug
An absent or malformed subordinate range commonly appears later as an ID-map or ownership error during image extraction or container startup. Follow the distro’s documented method to allocate ranges; do not paste a range that overlaps an existing user. After making a change, start a fresh login/session and repeat podman info as the same user. Do not use sudo podman as a shortcut: that changes the engine identity and can make a rootless issue appear to disappear while creating a second, unrelated container store.
Rootless mode does not mean that every host feature is available. The kernel, user namespace configuration, installed helper tools, network backend, and filesystem all constrain what can be mounted or managed. Validate the capabilities needed by the actual workload instead of assuming rootless implies full parity with a rootful daemon or a cloud container service.
Check cgroup v2 and resource behavior
Podman and systemd integrate through Linux cgroups. Quadlet, Podman’s systemd generator for declarative container units, requires cgroup v2 according to current Podman documentation. WSL distributions commonly use the unified hierarchy, but verify the live mount rather than trusting an old configuration snippet:
findmnt -T /sys/fs/cgroup -o TARGET,FSTYPE,OPTIONS
stat -fc %T /sys/fs/cgroup
podman info --debug
cgroup2fs indicates a unified cgroup v2 mount. Podman’s info output provides its own view of the cgroup version and manager. If they disagree, capture both outputs and check the distro’s WSL cgroup configuration and package version before changing it. Resource controls are hierarchical: an individual container cannot use more capacity than the parent cgroup or WSL VM permits. A container-level memory limit does not enlarge the VM budget configured on Windows.
Where the runtime and kernel support the requested controller, exercise a small, disposable container with a modest limit and inspect its cgroup placement and behavior. Do not use a production workload as the first test. Podman --memory and --cpus options configure container limits, but actual enforcement and visible counters depend on the active cgroup hierarchy and host limits. Validate with podman inspect, process/cgroup observations, and the workload’s measured response. If resource metrics are unavailable, distinguish that from the container failing to start.
Put storage on the Linux filesystem
Rootless image layers, writable layers, and volumes are stored under a per-user graph root reported by podman info. Keep that graph root and build contexts on the distro’s Linux filesystem, such as /home/alice, unless a measured and supported need says otherwise. A project under /mnt/c crosses the Windows/Linux filesystem bridge; bind mounts and many small-file build operations can behave differently from Linux-native ext4. This is a storage-placement choice, not a requirement to move every file in Windows.
Check free space both inside Linux and on the Windows volume backing the WSL virtual disk. Removing Podman images and containers frees logical filesystem space, but the host .vhdx may not immediately shrink. The WSL disk lifecycle and compaction process is separate from podman system prune; do not conflate guest free blocks with host file size. Before deleting cache or volumes, inspect what owns the data and confirm that no named volume contains state you need.
Use a short disposable workload to verify the local engine before building a compose workflow:
podman run --rm docker.io/library/alpine:latest sh -c 'id; uname -a'
podman ps -a
The first command should run as the rootless engine and clean up the exited container due to --rm. The container reports the guest kernel, not a separate per-container kernel. Record the image reference and digest when a reproducible test matters; a mutable tag such as latest is convenient for smoke testing but not a pinned production dependency.
Use systemd only for the lifecycle you need
Podman is commonly used as a daemonless CLI: a local podman run does not require a permanent Docker-style engine daemon. The Podman API service is a separate component, useful for clients that need Docker-compatible or Podman APIs. When systemd is available, Podman documents socket activation for that API. For rootless use, the user socket is typically podman.socket:
systemctl --user start podman.socket
systemctl --user status podman.socket
printf '%s\n' "$XDG_RUNTIME_DIR/podman/podman.sock"
Only enable the API socket if a client actually needs it. A socket being active does not mean a container is running; inspect containers with podman ps and inspect the application service separately. If the user manager is absent, check that systemd is enabled and the user session is initialized before diagnosing Podman itself. Linger settings can affect user-manager behavior after a login session ends on conventional Linux systems, but they do not force WSL to stay running after its own lifecycle ends.
For a declarative long-running container, use Quadlet when the distro’s Podman package includes the generator and cgroup v2 is active. Place a .container source file in the documented user Quadlet directory, for example ~/.config/containers/systemd/wsl-nginx.container:
[Unit]
Description=Local WSL demonstration web server
[Container]
Image=docker.io/library/nginx:alpine
PublishPort=127.0.0.1:8080:80
[Service]
Restart=on-failure
[Install]
WantedBy=default.target
Reload the user manager and start the generated service:
systemctl --user daemon-reload
systemctl --user start wsl-nginx.service
systemctl --user status wsl-nginx.service
podman ps
curl --fail http://127.0.0.1:8080/
The exact Quadlet support and generated unit behavior are tied to the Podman version shipped by the distro; consult its podman-systemd.unit documentation if the generator rejects a key. The service exists only while the user manager and WSL distro are running. A WantedBy=default.target relationship can start it when that manager starts, but cannot independently wake a stopped WSL instance. Test wsl --shutdown and next-launch behavior before relying on the container for a scheduled job.
Diagnose failures from the nearest layer outward
If an image pull fails, separate DNS/proxy/registry access from runtime startup by running a simple Linux curl or name lookup to the registry endpoint. If extraction fails, inspect free space, subordinate IDs, storage driver, and filesystem placement. If the container is created but cannot use expected resource controls, compare findmnt, podman info, the container’s cgroup, and the WSL VM budget. If the Podman API client cannot connect, check whether it targets the local rootless socket or a Windows/Podman Desktop remote connection, then inspect systemctl --user status podman.socket and its journal.
For a Quadlet that fails, run systemctl --user status wsl-nginx.service, inspect journalctl --user -u wsl-nginx.service, and verify the source file path and generator output. A failed pull on first start can exceed systemd’s default start timeout; Podman documents pre-pulling an image or configuring an appropriate startup timeout. Avoid enabling the generated service directly as if it were a hand-authored unit; the Quadlet source is the durable configuration, and the generator translates it.
Acceptance checks for a useful WSL setup
Before calling the environment ready, confirm all of the following under the intended Linux user: the CLI points at the distro-local engine; podman info reports rootless mode; subordinate IDs and helper binaries are present; /sys/fs/cgroup is cgroup v2 if Quadlet/resource control requires it; storage is on the intended Linux filesystem with enough free space; a disposable image runs and exits; and systemd units behave correctly during distro shutdown and relaunch. Record versions and output so a future kernel, WSL, or Podman upgrade can be compared.
This is a practical container-development environment, not a guarantee of production-host equivalence. The strongest setup is the one whose engine identity, cgroup hierarchy, storage location, and lifecycle are measured rather than assumed. Rootless Podman is a particularly good fit when developers want the CLI and systemd workflow inside their Linux distro, provided they keep WSL’s independent startup and shutdown boundary in the design.
Related:
- How to Set Up Docker with the WSL2 Backend
- WSL 2 and cgroup v2: Resource Control Inside the Linux VM
Sources: