IntroductionĀ
If youāre building .NET Aspire projects on Windows, you might be getting frustrated with the performance of your Docker containers. As a cross-platform developer, I switch between Windows and macOS frequently, and Iāve noticed a significant performance gap between the two platforms. Fortunately, thereās a simple solution to give your Docker containers a performance boost.Ā
Yes and no. Iāve come across this performance discrepancy between code running on macOS and code running on Windows before, specifically in Node applications. I donāt develop them, but I do run them sometimes. For example, my website is built with Jekyll, a Ruby-based static site generator. Being able to run it locally and preview posts before I publish them is fairly important, but the performance on Windows is so bad that it makes it almost impossible. This was frustrating, especially considering how snappily it ran on my M1 MacBook. Iāve since upgraded to a newer machine, but I mention this to highlight that itās not just a hardware issue. My Windows machine is an i9 with 32GB of RAM, but would get smoked by my 8GB M1. I didnāt think it could be purely a hardware issue, and I also figured that Node developers canāt exclusively be using macOS, so I did some digging.
While Node does feature some optimisations for ARM processors, itās not just the hardware that makes a difference, itās the OS too – lowish spec Linux machines can run Node without any trouble, in fact even a Raspberry Pi can run a Node server without breaking a sweat. (Note that Iām talking about development builds – production builds donāt tend to have this problem on Windows).
Diving into the nuts and bolts of why Node has such a hard time on Windows compared to macOS and Linux is beyond the scope of this article, but itās enough to know that Linux can run Node efficiently, and that means that my Windows hardware can at least in principle do it as well.
Fortunately, you donāt need to switch to Linux. With WSL, you can get the best of both worlds.
WSL2
If youāre reading this post, you almost certainly know what Windows Subsystem for Linux (WSL) 2 is, but in case you donāt, itās a way to run a Linux kernel on Windows. Itās not a hardware-emulated VM, but rather a translation layer, which provides a lightweight way to run Linux binaries on Windows. With WSL 2, you can even install your favourite Linux distro from the Microsoft Store. I tend to use Ubuntu, purely because its popularity means that itās well supported and has a large community, so when I encounter issues, I can usually find a solution quickly.
When you set up WSL, you get access to your Windows filesystem as a mounted drive, and your Linux filesystem is accessible from Windows too. At first, I tried navigating in my bash terminal to the folder where my Node project was stored and running from there. But this would often not work at all, and when it did, it was at best still slow, and at worst riddled with countless errors. Thereās a simple fix for this though, and thatās to run your project from the Linux filesystem.
Instead of navigating to my repo in the Windows filesystem, I just cloned it to the WSL filesystem and ran it from there instead.
Once I did this, I got performance that was on par with macOS. Well, to be fair, I havenāt actually benchmarked these, but the difference between running natively from Windows is night and day, and to be honest Iām not really fussed about the specifics. The point is, itās a usable solution for me, and thatās really all that matters. As thereās no perceptible difference for me now between running Node on WSL 2 or macOS, I havenāt felt the need to run any benchmarks.
Docker
As with WSL, if youāre reading this then you also already know what Docker is. But an important point to note is that, like with WSL, Docker isnāt a hypervisor, and containers arenāt VMs. This means that you can potentially be limited from running certain containers on Windows, especially if theyāre Linux-based (the reverse is also true). This is where WSL 2 comes in handy, as it allows you to run Linux containers on Windows.
When you install Docker, you can choose between using WSL 2 as the backend, or the Windows Hypervisor Platform (the same service that runs Hyper-V). WSL 2 is the recommended option, by Docker and Microsoft, for good reasons which I wonāt go into here (but in short itās much lower overhead – with WHP, youāre running a full hardware-emulated VM, with WSL 2, youāre running a lightweight Linux kernel).
Most of the time, this runs buttery smooth. As I mentioned, this is the recommended setup and usually results in pretty good performance.
.NET Aspire
I often describe .NET Aspire as ādocker compose in C#ā. This is a gross oversimplification – itāsāÆwayāÆmore than that, and itās a bit of a disservice to describe it that way. But itās also not inaccurate – .NET AspireāÆdoesāÆallow you to define your services in C#, and itāÆdoesāÆrun them in Docker containers.
What this means is that your Aspire services run as Docker containers, and generally they run pretty well. However, if youāre running them on Windows, you might occasionally run into some performance issues. I wouldnāt describe these as showstoppers, and in fact it was only when I switched a particular project to macOS that I noticed the difference. But itās there, and itās noticeable, and this became important for a client project where I needed to frequently run demos. The performance was fine for my developer experience, but some of the delays in the demos were not conducive to convincing the client that the solution was adequate.
The Fix
It turns out, you canāt just run your Aspire projects from WSL. This is a shame – I think a lot of people would like to see this. If you look on the Aspire repo, you can see itās a popular request. But it turns out itās not as simple as it seems -āÆthis issueāÆprovides some insight into some of the challenges, andāÆthis discussionāÆcontains a link to the filtered list of WSL issues on the repo, highlighting the problems faced by developers trying to run Aspire projects from WSL.
But it turns out you can still gain performance improvements in Docker without running your Aspire project from your WSL filesystem. The fix, it turns out, is actually to limit the memory and CPU usage of WSL.āÆThis StackOverflow questionāÆ(and the replies) provide the fix. As you can expect, a popular answer (although not the accepted one) recommends running from your Linux filesystem. This is what Iāve always done, but as mentioned, this doesnāt work for Aspire projects.
The accepted answer states that you can create aāÆ.wslconfig
āÆfile in your user profile, shut down Docker desktop (essentially you have to make sure theāÆVMMemāÆprocess is not running; you may have to kill it manually), and then restart Docker. This will apply the settings in theāÆ.wslconfig
āÆfile, which can limit the memory and CPU usage of your Docker containers. You may have to tweak it to figure out what works best for you, but for me I simply left the default of one processor, and set the RAM limit to 6GB:
[wsl2]
memory=6GB #Limits VM memory in WSL 2 to 6GB
processors=1 #Makes the WSL 2 VM use one virtual processors
Depending on your workload you may need to adjust this. I wanted to start with these and tweak as needed, but in my case I found this worked well enough.
I must admit I was sceptical, but it really does work. Again, I havenāt done any benchmarks here, but let me give you an anecdote. A solution I am currently working on for a client involves importing documents to be used as templates for reports. In my Aspire infrastructure, Iām running the Cosmos DB emulator for persistence and Azurite for blob storage. In the UI, you can select a file for upload, and once itās uploaded it will process in the background. Once the file has been processed, the UI updates to show the file.
On macOS, when I upload a file, it appears in the UI straight away. On Windows it would take several seconds. Since making this change, Iāve noticed a significant performance improvement. Itās still not instant, but thatās because Iāve introduced another step in the process (uploading to SharePoint rather than Azurite), so I will need to run it again on macOS to compare, and in this case, I may need to run the benchmarks after all!
Even so, itās been a huge improvement and, counter-intuitive as it may be, it works and Iām happy with the results.
Why does this work?
As I said, this seemed counter-intuitive. As others noted in response to that answer, the issue isnāt with Windows performance, itās with container performance. I would understand if the issue was that when running Docker containers, my Windows machine was struggling to keep up, but that didnāt seem to be the case.
But it turns out that actuallyāÆwasāÆthe problem. Without aāÆ.wslconfig
āÆfile, WSL will consume all available CPU and memory (the same thing happens with SQL Server and Exchange Server, which is why theyāre not recommended to run on the same machine). As Docker runs on top of WSL, when the containers are running, WSL consumes all available resources and starves the OS. Since WSL doesnāt release memory back to Windows easily, it forces the OS to use disk-based virtual memory (swapping), which is much slower than RAM.Ā
Where this sneaks under the radar for developers like me, is that while this is happening, youāre typically debugging code running in a container. When you stop, things usually go back to normal (but not always – sometimes you have to kill WSL to get it to release the resources), and you donāt notice any performance issues in your OS. This is why itāÆseemsāÆlike the issue is just with Docker – and in a sense, it is, but the problemāÆdoesāÆin fact impact the whole OS. I just wasnāt noticing.
Conclusion
If youāre running Aspire projects on Windows and youāre noticing performance issues, try limiting the memory and CPU usage of your Docker environment through aāÆ.wslconfig
āÆfile. Itās a simple fix that can make a big difference. In fact, even if youāre not running Aspire, you might find this useful for anything where youāre using Docker on WSL 2, or even anything with WSL 2 in general.Ā
Iām may be late to the party with this one, but it made a huge difference for me, and I hope it does for you too.
Do you have any other performance improvement suggestions for Aspire on Windows? Let us know in the comments below!