I've always developed in a Windows environment, but I've always been fascinated by the Linux world, so much so that most of my projects were then deployed on Linux. What's always disappointed me, however, is how Windows handles the file system with heavy loads related to databases or uncompiled runtime code. For this reason, despite developing in Windows for many years, I've used Linux virtual machines (VMWare or VirtualBox) only for database servers.
I've used many IDEs, moving from simple Notepad to the Visual Basic IDE to Eclipse to NetBeans, and finally landing on Visual Studio Code. VSCode is the perfect environment for what I'm doing now, using the most modern languages and development tools. In particular, I believe Docker was a turning point, not just for me but for the entire developer community. So I installed Docker Desktop and started developing my first projects. I created my own stacks, using vscode with remote devcontainers, but I quickly realized that the system was slow and unreliable, with crashes, slowdowns, and excessive memory usage. I was envious of other developers using Linux or Mac, where everything "seemed" to work perfectly:
- Responsiveness
- Very fast file system volumes
- Very few crashes
Switching to Linux wasn't an option, so I decided to use wsl2 and rethought my entire strategy. I uninstalled Docker Desktop, installed a Debian Trixie wsl distro for development, and an Ubuntu distro. I installed docker on Debian, installed all the tools I needed—git, npm, etc.—and created my own project folder and integration with vscode. Like magic, you move to the workspace and type "code .". And voilà, if you've installed the right extensions on VSCode, everything works perfectly.
So far, nothing new. Now many of you will say: well done, you've discovered hot water (an Italian idiom for stating the obvious). Was there any need to write a post about things we expert developers have been doing for years? Sorry guys, I know I haven't discovered anything new, but maybe there's something from this point on that might be of interest—maybe not to everyone, but I believe and hope to many of you who develop on WSL2 in a Windows environment.
As I was saying, everything's perfect, or maybe not. I started having some doubts when I realized my development distro had grown to 60GB. Oh yes, because Docker is no joke; with images, layers, and caches, it fills up quickly.
Solutions? Not many. The first that came to mind was having multiple distros, one for each project. Hmm! That's not my style. I prefer to have a nice environment with everything I need. Let's analyze the problem...
Who "bloats" the filesystem?
- Docker data
- Data volumes
In a "bare-metal" Linux environment, we can separate the disks and mount them to achieve context separation. Well, that was my solution:
- The distro that contains only the OS and development tools
- An external disk for Docker data and work (images, layers, cache, etc.)
- A possible external disk for storage
Easy! No, or at least not that easy, because Docker data is on /var/lib/containerd or /var/lib/docker, so we need to create a VHDX disk, move the data from the original distro, delete the data from /var/lib/containerd, and create the mount point to host the newly created external disk. And the solution is less simple than you might assume from these simple points.
Okay, you're probably tired of all this chatter, so let's get to the solution. Let's define the operational steps for maximum security and then see how to implement them.
- Export the original distro so as not to operate on our existing system (obviously, this isn't necessary if you're starting on a new distro).
- Import the distro with a different tag to recognize it.
- Create an empty vhdx disk (ext4) from Windows.
- Mount the disk from Windows (
wsl --mount) to make it visible to the distro. - Log into the distro and temporarily mount the new disk (e.g., on
/mnt/containerd). - Stop the
containerd,docker, anddocker.socketservices if they are running. - Copy
/var/lib/containerdto/mnt/containerdwithrsync. - Rename
/var/lib/containerdto/var/lib/containerd.bak. - Create the mount point
/var/lib/containerd.
IMPORTANT!!! Before proceeding further, you need to disable the autostart of the containerd, docker, and docker.socket services. You'll understand why, though many of you may already be guessing. At this point, you can close the distro with exit.
We have our separate OS and large data on an external VHDX. Now we need to test that everything works:
- Log into the distro
- Mount the disk
- Run some docker commands
- If everything works, delete
/var/lib/containerd.bak - Automate the process
Below are step-by-step instructions based on my system, easily adaptable to your needs.
Step 1: Preparation
1. View existing distros
wsl --list -v
2. Shut down WSL to ensure data consistency
wsl --shutdown
3. Export your current distro (e.g., Debian) as a backup
wsl --export Debian C:\wsl\debian_backup.tar
4. Import the distro into a new folder to create the "Sandbox" environment
wsl --import DebianDev C:\wsl\DebianDev C:\wsl\debian_backup.tar
Step 2: Create the VHDX Disk (PowerShell)
1. Create the 64GB dynamic virtual disk.
Warning: New-VHD is only available if Hyper-V is enabled. Alternatively, use diskpart or qemu-img.exe.
New-VHD -Path "C:\wsl\debian\docker-data.vhdx" -SizeBytes 64GB -Dynamic
2. Mount the disk on WSL as a bare device (without mounting it in the Windows filesystem)
wsl --mount "C:\wsl\debian\docker-data.vhdx" --bare
Step 3: Migration (Within the WSL Distro)
wsl -d DebianDev
1. Identify the new disk (usually /dev/sdc or sdd)
lsblk
2. Format the disk in ext4 (WARNING: Verify the correct device)
sudo mkfs.ext4 /dev/sdc
3. Create a temporary mount point and mount the disk
sudo mkdir /mnt/containerd
sudo mount /dev/sdc /mnt/containerd
4. Stop Docker services and disable autostart (essential!)
sudo systemctl stop docker.socket docker containerd
sudo systemctl disable docker.socket docker containerd
5. Move data with rsync to preserve permissions and links
In newer Docker and containerd installations, use /var/lib/containerd/
sudo rsync -avPH /var/lib/containerd/ /mnt/containerd/
6. Rename the old folder for security
sudo mv /var/lib/docker /var/lib/containerd.bak
7. Create the final mount point
sudo mkdir /var/lib/containerd
sudo umount /mnt/containerd
exit
At this point we can test it from the terminal as administrator:
wsl --list -v # check that DebianDev is present.
wsl --shutdown # for safety, not necessary if everything stopped.
wsl --mount --vhd C:\wsl\debian\docker-data.vhdx --bare
wsl -d DebianDev
sudo mount /dev/sd(x) /var/lib/containerd # where x is the 64 GB disk device.
sudo systemctl start containerd
sudo systemctl start docker
docker ps
docker images # if the images are visible, everything is fine.
sudo rm -rf /var/lib/containerd.bak # (we still have 2 backups)
exit
Automating the Mount
Now for the part that gave me a headache. Automate everything with a simple click. It seems easy at first glance, but unfortunately, after many failed attempts due to race conditions on the services that started before the disk was mounted, I adopted this solution. There are others, but this is mine. I created a bash script.
Before creating the script, you need to find the exact UUID of your newly created ext4 disk. You can do this by running blkid:
sudo blkid
Look for the UUID associated with your disk (e.g., /dev/sdc). Keep this UUID handy, as you will need it to replace in the script below.
#!/bin/bash
# /usr/local/bin/mount-containerd.sh
LOGFILE=/var/log/mount-containerd.log
echo "$(date) - Starting mount-containerd" >> $LOGFILE
# 1. Stop services first
echo "$(date) - Stopping services" >> $LOGFILE
systemctl stop docker.socket
systemctl stop docker
systemctl stop containerd
# 2. Clean the mount directory
echo "$(date) - Cleaning /var/lib/containerd" >> $LOGFILE
rm -rf /var/lib/containerd/*
# 3. Mount the VHDX
for i in $(seq 1 15); do
DEVICE=$(blkid -U <YOUR-UUID-HERE> 2>/dev/null)
if [ -n "$DEVICE" ]; then
echo "$(date) - Device found: $DEVICE at attempt $i" >> $LOGFILE
mount "$DEVICE" /var/lib/containerd
echo "$(date) - Mount done" >> $LOGFILE
# 4. Restore services
systemctl start containerd
echo "$(date) - Services started" >> $LOGFILE
exit 0
fi
sleep 2
done
echo "$(date) - ERROR: device not found after 30 seconds" >> $LOGFILE
exit 1
I chose /usr/local/bin, but you can put it wherever you want.
Edit /etc/wsl.conf:
[boot]
systemd=true
command = "/usr/local/bin/mount-containerd.sh"
[interop]
enabled=true
appendWindowsPath=true
[user]
default=node # change with your work user, or comment if you're running as root.
Exit WSL and create the file C:\wsl\debian\start-dev.bat:
@echo off
:: Execute as administrator or wsl --mount doesn't work
:: Mount the VHDX
wsl --mount --vhd C:\wsl\debian\docker-data.vhdx --bare >nul
:: Open the distro
wsl -d Debian-Dev --cd ~
Right-click and run as administrator.
Important: With this method, you won't be able to launch wsl -d DebianDev from the terminal without first mounting the disk. This is an acceptable solution for me, but if it isn't for you, there's a workaround. Create a task in Task Scheduler that, when the system or your user starts, runs the wsl --mount of the vhdx, at which point the distro will behave like all the other WSL distros because it will find the disk already available without using start-dev.bat.
For added security, I added this simple block to the shell start script (in my case, zsh):
# --- CONTAINERD MOUNT CHECK ---
if ! mountpoint -q /var/lib/containerd; then
echo "⚠️ WARNING: docker-data.vhdx not mounted!"
fi
N.B. In addition to the docker data, we could also create an external disk for storage volumes using the same procedure. Just add the mount to /usr/local/bin/mount-containerd.sh.
Last but not least, to recover space on the main disk, you'll need to compact it with diskpart because the space deleted from /var/lib/containerd.bak is not automatically recovered. I've tried to make this guide as simple and clear as possible, but I understand there are a lot of steps involved; I hope I haven't made any mistakes. If you have found different solutions, write it in the comments, or let's discuss it together.
Side notes: pre requisites based on my current implementetion
- WSL version >=0.67.6 o native support to systemd, enbled by /etc/wsl.conf.
- Docker Engine latest version to have native containerd instead of traditional /var/lib/docker
If you have different versions the logic remains the same.
🇮🇹 Nota di Trasparenza dell'Autore
L'architettura, gli script e le strategie presentate in questo articolo sono originali al 100%, frutto della mia esperienza diretta. Ho concepito e scritto l'intero pezzo nella mia lingua madre, l'italiano. Ho utilizzato un LLM esclusivamente come mero strumento di traduzione verso l'inglese, per rifinire la terminologia tecnica e rendere il contenuto accessibile alla community internazionale.
🇬🇧 Author's Transparency Note
The architecture, scripts, and strategies presented in this article are 100% original, born from my direct field experience. I conceptualized and wrote the entire piece in my native language, Italian. I used an LLM strictly as a translation tool to adapt the content into English, refining the technical terminology to make it accessible to the global community.
Top comments (1)
Thanks for sharing