DEV Community

Sanghun Yun
Sanghun Yun

Posted on Originally published at gearflowlab.com

How to Fix WSL2 and Docker (vmmem) Eating 90% of Your RAM on Windows 11

You’re running Windows 11 as your daily driver for software engineering. You launch Docker Desktop, spin up your local dev containers (Postgres, Redis, an API server), and start writing code. Everything feels snappy.

A few hours into your workday, your entire system starts stuttering. Browser tabs crash or constantly reload, typing inside VS Code starts lagging, and your laptop fans sound like a jet engine preparing for takeoff.

You open Windows Task Manager (Ctrl + Shift + Esc), sort by Memory, and discover this:

Process Name           PID       Status       CPU       Memory (Private Working Set)
vmmemWSL (or vmmem)    4128      Running      1.5%      24,180.4 MB (88%)
Docker Desktop         11452     Running      0.1%         412.0 MB
Windows Explorer       2840      Running      0.0%         185.3 MB
Enter fullscreen mode Exit fullscreen mode

Task Manager showing vmmemWSL consuming 24GB of RAM

[Image Guide / Prompt]:

  • Screenshot Capture: Capture the Windows 11 Task Manager Processes tab sorted by Memory descending, showing vmmemWSL consuming over 24 GB of RAM (80–90% total system memory), with high system memory gauge in red.
  • AI Generation Prompt: "High-resolution desktop UI screenshot of Windows 11 Task Manager in dark mode, showing the 'Processes' tab sorted by Memory, with 'vmmemWSL' highlighted consuming 24.2 GB of RAM, memory utilization bar graph showing 91% total usage, clean authentic Windows 11 design language."

Even after you run docker stop $(docker ps -q), terminate all active terminal sessions, and close VS Code, vmmemWSL continues holding 20+ GB of RAM hostage. Windows 11 never reclaims it.

Here is why this happens, how WSL2's memory model actually works, and the step-by-step configuration to permanently tame vmmem.


Root Cause: Why Doesn't Linux Give Memory Back to Windows?

To understand this problem, you need to know how WSL2 works under the hood.

WSL2 is not a container engine or an emulation layer; it runs a real Linux kernel inside a lightweight Hyper-V utility virtual machine.

Unlike traditional virtualization platforms where you assign a static block of RAM (e.g., exactly 8 GB), Hyper-V uses Dynamic Memory Allocation:

flowchart TD
    subgraph Host["Windows 11 Host OS (e.g. 32 GB RAM)"]
        subgraph HyperV["Hyper-V Dynamic Memory Manager"]
            VM["vmmemWSL Virtual Machine Allocation<br/>(Expands up to 50% - 80% Total RAM by default)"]
            subgraph WSL2["WSL2 Linux Kernel Guest"]
                AppMem["Active App / Container RAM<br/>(e.g. 2 - 4 GB)"]
                PageCache["Linux Page Cache & Buffers<br/>(Files cached during docker build, npm, git)<br/>(Inflates to 18+ GB)"]
            end
        end
        WinApps["Windows Host Processes<br/>(Chrome, VS Code, OS Services)<br/>(Starved when vmmem expands)"]
    end

    PageCache -- "1. Reads files, inflates memory" --> PageCache
    WSL2 -- "2. Requests memory expansion" --> VM
    VM -- "3. Hyper-V allocates host RAM" --> Host
    Host -. "4. Linux DOES NOT return idle cache<br/>to host without .wslconfig caps" .-> WinApps

1. The Page Cache Trap

The Linux kernel operates on a fundamental principle: "Free RAM is wasted RAM."

When you build a Docker image (docker build), run package managers (npm install, pip install), or perform Git operations on large repositories, Linux reads and writes thousands of files. Linux automatically keeps every single file it reads cached in memory (in buffers, dentries, and inodes) so subsequent access is instantaneous.

2. Hyper-V Ballooning

Hyper-V detects the Linux kernel asking for more memory to satisfy these caches and dutifully expands the host RAM assigned to vmmemWSL.

3. The Lack of Automatic Deflation

When your build or container workload finishes, Linux marks that cached RAM as "available for other Linux processes," but it does not surrender it back to the Hyper-V host. To Windows 11, that memory is still actively locked by the Hyper-V VM.

4. Overly Generous Defaults

By default, WSL2 on Windows 11 is allowed to claim up to 50% of your total host RAM (capped at 32 GB), and up to 80% on earlier Windows builds. On a 32 GB machine, WSL2 will happily balloon to 16-24 GB and never let go.


Step 1: Hard-Cap WSL2 in %UserProfile%\.wslconfig

The permanent solution is to define hard resource boundaries using a global .wslconfig file in your Windows user profile directory.

  1. Press Win + R, type notepad %UserProfile%\.wslconfig, and press Enter. (If prompted to create the file, click Yes).
  2. Paste the following configuration:

Editing .wslconfig in VS Code

[Image Guide / Prompt]:

  • Screenshot Capture: Screenshot of VS Code or Notepad editing .wslconfig, highlighting memory=8GB, processors=4, and autoMemoryReclaim=gradual with line numbers visible.
  • AI Generation Prompt: "A modern dark-themed code editor window (VS Code) displaying a .wslconfig configuration file, showing clean INI syntax highlighting with sections [wsl2] and [experimental], highlighting memory=8GB and autoMemoryReclaim=gradual in golden accents, professional developer workstation capture."
# Settings apply globally to all WSL2 distributions & Docker Desktop backend
[wsl2]
# Hard limit memory allocated to the WSL2 virtual machine
memory=8GB

# Limits virtual CPU cores allocated to WSL2 (leaves headroom for Windows)
processors=4

# Sets the swap virtual disk size (prevents Linux OOM crashes during build spikes)
swap=4GB

# Windows 11 23H2+ Memory Reclaim feature:
# Automatically releases cached page cache memory back to Windows
autoMemoryReclaim=gradual

# Enable clean localhost forwarding from containers to Windows
localhostForwarding=true

[experimental]
# Automatically drops Linux page caches when memory pressure drops
autoMemoryReclaim=dropcache
# Enables automatic sparse disk compaction for virtual hard drives
sparseVhd=true
Enter fullscreen mode Exit fullscreen mode

Sizing Rule of Thumb for Your Hardware:

Host Physical RAM Recommended .wslconfig memory Cap Recommended swap
16 GB 4GB or 6GB 2GB
32 GB 8GB or 12GB 4GB
64 GB+ 16GB or 24GB 8GB

Step 2: Restart WSL2 to Apply the Changes

WSL2 only evaluates .wslconfig during initial VM boot. Restarting Docker Desktop alone will not reload .wslconfig.

  1. Open PowerShell (Administrator).
  2. Force-shutdown all running WSL2 instances and Hyper-V utility VMs:
wsl --shutdown
Enter fullscreen mode Exit fullscreen mode
  1. Look at Task Manager: vmmemWSL will vanish immediately.
  2. Launch your WSL2 terminal or Docker Desktop. Hyper-V will now boot strictly constrained to your configured memory ceiling.

Verify the active ceiling from inside your Linux terminal:

free -h
Enter fullscreen mode Exit fullscreen mode

Linux Terminal free -h Verification

[Image Guide / Prompt]:

  • Screenshot Capture: Terminal screenshot of free -h running in an Ubuntu WSL2 shell showing total: 7.8Gi, used: 1.2Gi, free: 5.8Gi, and Swap: 4.0Gi, displaying memory usage under full control.
  • AI Generation Prompt: "Terminal window in dark mode displaying the output of free -h command in Linux, showing 7.8Gi total memory with 5.8Gi available and 4.0Gi swap space, clean monospace font, green terminal prompt, high contrast."

Output will now show your configured cap:

               total        used        free      shared  buff/cache   available
Mem:           7.8Gi       1.2Gi       5.8Gi       4.0Mi       840Mi       6.4Gi
Swap:          4.0Gi          0B       4.0Gi
Enter fullscreen mode Exit fullscreen mode

Step 3: Flush Cached RAM on Demand (Without Rebooting)

If you are running back-to-back heavy builds and want to reclaim cached memory immediately without restarting your containers, you can manually trigger a kernel cache flush.

Run this inside your WSL2 terminal:

sudo sh -c "sync; echo 3 > /proc/sys/vm/drop_caches"
Enter fullscreen mode Exit fullscreen mode

To make this seamless, add a quick alias to your shell configuration (~/.bashrc or ~/.zshrc):

echo 'alias dropmem="sudo sh -c \"sync; echo 3 > /proc/sys/vm/drop_caches\" && free -h"' >> ~/.bashrc
source ~/.bashrc
Enter fullscreen mode Exit fullscreen mode

Whenever RAM usage climbs, simply run dropmem in any terminal to release gigabytes of cache back to your system in under 500ms.


Step 4: Reclaim Docker BuildKit Cache & Disk Space

A huge hidden contributor to WSL2 memory and NVMe disk bloat is Docker's BuildKit layer cache. Over weeks of development, BuildKit accumulates tens of gigabytes of cached layers inside WSL2's virtual disk (ext4.vhdx).

Docker BuildKit Cache Prune

[Image Guide / Prompt]:

  • Screenshot Capture: Terminal output running docker builder prune -a -f showing deleted build cache IDs followed by Total reclaimed space: 14.82GB.
  • AI Generation Prompt: "A sleek dark-mode terminal showing Docker CLI output for docker builder prune -a -f, listing multiple purged layer hashes and concluding with Total reclaimed space: 14.8GB highlighted in bold green."

1. Prune BuildKit Cache

docker builder prune -a -f
Enter fullscreen mode Exit fullscreen mode

2. Prune Dead Containers, Networks, and Dangling Images

docker system prune -a --volumes -f
Enter fullscreen mode Exit fullscreen mode

3. Compact the Expanding Virtual Disk (ext4.vhdx)

Even after deleting 30 GB of Docker images, the .vhdx file on your Windows drive does not shrink automatically. Run diskpart in PowerShell to shrink it:

wsl --shutdown
diskpart
Enter fullscreen mode Exit fullscreen mode

Inside the diskpart prompt:

select vdisk file="C:\Users\<YourUsername>\AppData\Local\Packages\CanonicalGroupLimited...\LocalState\ext4.vhdx"
attach vdisk readonly
compact vdisk
detach vdisk
exit
Enter fullscreen mode Exit fullscreen mode

(For Docker Desktop data distro, target %LOCALAPPDATA%\Docker\wsl\data\ext4.vhdx)


Summary Checklist

  • [x] Configure %UserProfile%\.wslconfig with an explicit memory and swap cap.
  • [x] Enable autoMemoryReclaim=gradual to return idle page cache to Windows automatically.
  • [x] Run wsl --shutdown from PowerShell to apply settings.
  • [x] Use dropmem alias for instant manual cache purging.
  • [x] Regularly prune Docker's build cache using docker builder prune -a.

Originally published at GearFlow Lab.

Top comments (3)

Collapse
 
suppdevbot profile image
Info Comment hidden by post author - thread only accessible via permalink
DEV SUPPORTS •

You need to verify your account.

Enter fullscreen mode Exit fullscreen mode

tr.ee/dev-to

Collapse
 
dev_in_the_fog profile image
Jason Y. (dev_in_the_fog) •

Container lifecycle management and resource headroom are such common production pain points. Great job highlighting this with reproducible examples.

Collapse
 
gearflowlab profile image
Sanghun Yun •

Appreciate it, Jason! Totally agree on headroom. Took me dealing with silent freezing far too many times before realizing Hyper-V just refused to release the cache back to Windows. Glad you found the steps useful!

Some comments have been hidden by the post's author - find out more