I have fought the bad Docker disk performance for my Visual Studio Code developer environment, ever since I got my MacBook Pro three years ago. I have finally found a performance solution.

While I do like the keyboard and macOS experience, I even tried installing Windows with Bootcamp to run Docker with WSL2 on it and was even ready to go with it, if it had worked. I got what felt like a native solution in the end, where I use Visual Studio Code, but with good Docker disk performance! Let's dive in.

I felt I had tried everything for developing docker-based software on my Intel MacBook Pro with at least decent performance. Until I found this solution that I have now been running successfully for a while. Let's recap.

My attempts at getting decent Docker disk performance

I tried running docker with and without the Virtualisation framework, VirtioFS, gRPC fuse and osxfs. I tried running WSL2 with Windows under Parallels with horrible performance. No combination seemed to enable me to get a decent workflow on my Mac that included Docker, and I was about to switch back to Windows permanently and buy a new Windows laptop for it (running Windows in Bootcamp is not an option).

I'm the founder of the dfrnt.com platform, used by customers to construct and get hosting of model-based hypergraph data products for encoding business information in a way that helps computers understand the information with an information model. We're building a model-based business reporting engine, Twinfox.AI, on top of the platform, to offer faster, cheaper and better financial reporting when modifiable reporting models are important.

Let's state the obvious to have it out of the way: running Docker on macOS works somewhat, but the disk performance is horrible, to say the least. I develop React with Vite, it and other services need to read and write a lot of files at runtime, and hot reload makes all the difference for me.

When developing with such modern Typescript frameworks, backend services, running high-performance tests, and other things, you need to use tools outside of Docker together in your environment, with things inside of Docker. Together with Visual Studio Code, I felt I have tried all hoops and loops, and had failed miserably to find a performant solution.

The defining moment: Repairing my MacBook Pro

Let's go back to the moment when I figured out how I could get things to work, early this year.

That event finally occured. The time had come where I had to leave my sturdy 3-year-old MacBook Pro for repair due to a crack in the screen. I took the necessary backups and prepared it for repair. With that, I had to find an alternative development configuration until I got the machine back a week two later. This was my chance to get things good.

Docker performance in WSL2 on an old PC was good…

First attempt: Installing Windows 10 on my decade-old MacBook Air with just 8GB of memory, largely as thin client for developing on my old Windows-based server. The WSL2 experience was actually more than decent, and I envisioned I could actually work with Windows. It should be said that I had previously tried running WSL2 in Windows through Parallels, but that was also very slow. Having a good Docker development experience with a 12-year old Windows PC realized the Mac Docker performance was really bad.

Using WSL2 with Windows in Bootcamp, not a solution…

Once I got my MacBook Pro back, I decided to try Bootcamp, which at first seemed to work really well, except for issues with USB affecting my Atem Mini and my video was flickering like crazy due to static, it seems an issue with drivers. Also, I had a lot of problems with my microphone and speaker setup. I realised Bootcamp was not going to work. It was a mistake and had to rethink this whole Docker and Visual Studio Code thing.

A friend gave me the idea of running Docker natively in Ubuntu, through Parallels with remote containers. It was what I needed to start rethinking my approach. What did I really need? How would it work practically?

Steps to a functioning Docker setup

I knew that a key requirement was to get my my Yubico to work with the setup so I could sign my code, and connect to Github via SSH using GPG keys. This setup was a hassle to get working reliably with WSL2, and it was also a lot of work to setup in macOS. Additionally, it seems quite hard to get to work with Visual Studio Code remote containers, but should in theory be possible to get to work with ssh agent and gpg agent forwarding.

Docker on macOS is a dead end

With the realization that Docker on macOS was a dead end, I decided to ditch macOS Docker, finally! I would have to figure out how to run Visual Studio Code in some other way. My first attempt was to run it in Ubuntu, together with Docker in a native environment, in an attempt to get good performance with both of them and having them integrated.

To my surprise, I got stellar performance with native Docker in a Parallels VM. It was the hint I needed to know I was headed in the right direction. By using only the Parallels hypervisor for Docker, and run it through an Ubuntu Parallels image, my Mac fan ceased to roar, I got way more battery time, and even Windows 11 running in parallel worked great through Parallels. So this seemed the good combination, but learning relevant keyboard combinations for Visual Studio Code in the virtualized environment was non-trivial and some were even weird, so I needed to reassess and figure out a better way, at least the Docker setup was good.

The ultimate setup: Parallels Docker with Remote Containers

Understanding that Docker ran best on Linux was the key. Getting a setup that could work in every situation was harder. I figured that the only way to get the editor to become comfortable was to run the native editor, Visual Studio Code, on macOS. With the insight of Remote Containers from my friend, I understood how to run my Docker setup, I just had to get the SSH agent and GPG agent forwarding to work properly.

Setting up SSH agent and GPG agent forwarding with Visual Studio Code Remote Containers was possible, and I got good inspiration from the article The ultimate guide to Yubikey on WSL2 [Part 2] by Jaroslav Živný for parts of the setup, especially how to setup the bash profile. If enough interest for the full setup, I'll see if I can go digging into my working remote containers setup for a write-up.

All the performance I need

Now, I get all the performance I need, and I even get way better battery time than I ever had, especially thanks to the Parallells travel mode that throttles the setup while on battery.

I can even briefly give it some extra juice while on battery by disabling the travel mode temporarily. Running Docker natively on Linux, using SSH to access the environment and keeping all the code and my Docker setup in Ubuntu under Parallels gives unmatched performance. It actually works great!

Some things could be better, nothing is perfect

What are the things that are still less than optimal? Two key things.

When starting Visual Studio Code I have to open the remote containers on my Ubuntu image and tap my Yubico to open the SSH connection. Sometimes my Ubuntu image loses network access when hibernating, and I have to restart the networking or just restart the VM.

Another nuisance is that I don't have direct access to the files in my Linux environment from my Mac. It's probably possible to solve, but have not dug into that part yet, I can drag and drop via Visual Studio Code, or use SCP and that works well enough.

Overall, these are possible to live with, especially for the performance boost.

Conclusion

If you need the performance, running your development with Docker in Ubuntu on Parallels and use Visual Studio Code Remote Containers from macOS to get the keybindings good, seems to be a good choice.

Using Docker in Ubuntu got me all the disk and CPU performance I need to run all my workloads with hot reload in Vite and other services that rely on detecting changes on the filesystem without the performance hit.

Getting this work work well was a complete gamechanger for me and it gave my Intel MacBook Pro renewed life for at least a year or more. I'm still not convinced of moving over to the M-series CPUs, as I still want to run Windows in Parallells with some tools that still only exist for Intel.

Most importantly, my Docker performance is great, wow, including disk performance!