Running Linux on MacOS
Macs seem to have become the device of choice amongst a lot of developers lately. I've been using one for a few years now. They don't require as much configuration as Linux and the ARM CPUs offer a lot of power at low energy consumption, which has made them very attractive.
But there comes a time when some development tasks require the flexibility of Linux. I ran into such a case recently as I was trying to learn more about the basics of kernel development. I'm sure it's possible to write a system call or a device driver on a Mac, but it's a lot less simple and a lot of the guides assume you're using Linux.
So my first question was, what's the easiest way to set up a VM with Linux on it on a Mac? I remember VirtualBox being a bit clunky to use, and VMWare is only partially free. I wanted a really lightweight way to just get access to a shell and the kernel development tools.
I'm going to talk in this article about how to set it up, as well as a few considerations that will guide your choice of configuration and OS images. Some of this information will be obvious to more experienced developers, but my hope is that it will help anyone who doesn't understand much about virtualisation technology. My explanation has been written to the best of my understanding. If you see any errors and wish to offer a correction, please feel free to email me at daliasblog@protonmail.com.
Containers vs Emulators vs Virtual Machines
The first challenge you might run into when research virtualisation is the variety of different technologies that come up in this context, sometimes not clearly distinguished by the people who write about them. There are three different types of technologies that often come up, which I'm going to explain.
Containers are a convenient way of separating processes into their own space on Linux (and technically BSD as well, coming from BSD jails). Strictly speaking, a container just provides segregation. An app running inside a container gets access to the underlying hardware, and is allowed to behave as if it has access to the whole operating system, when in reality it is restricted to a small portion of it. That's all a container is at a high level.
A virtual machine is quite different. It is an operating system, which runs inside the host operating system. It behaves as if it has access to the entirety of the underlying hardware. Generally virtualisation gives the guest operating system (the one inside the VM) restricted access to the hardware it is running on, similarly to the way a container gets access to the host operating system.
An emulator is a program which imitates hardware. It creates a whole new simulation of a physical computer on top of the operating system it is running inside of. If you are running a 64-bit machine, for instance, you can emulate a 32-bit machine on top of it. Similarly, you can emulate a CISC device on top of a RISC one (so an Intel chip on top of Apple's ARM chip for instance). This program receives the instructions the guest operating system passes to the CPU, and transates them into the host instructions to execute, before passing the results back to the guest, simulating working hardware. This is a more computationally expensive process than virtualisation or using containers, but obviously more versatile, in that it allows you to use different styles of hardware where necessary.
Containers on top of VMs on top of Emulators
Here's where it can get confusing. These technologies can all be combined. You might have noticed that you can run Linux OSes in containers using Docker on top of Mac, even running Intel versions of Linux on an ARM chip. How does this work, if a container is just segregating the processes and not emulating hardware?
Docker doesn't just package Linux containers. In addition to a bunch of fancy networking tools, it also combines virtualisation and emulation with containers. If you want to run Linux on a Mac, Docker will set up a Linux VM and run your containers on top of it (although Apple does appear to have a custom solution now that's even faster). And if you want to use a different architecture from your host, then you will end up running that VM on top of an emulator. This will obviously be slower, but also very useful for testing different configurations.
This is how the technology ends up being combined, and it's why the terms often get used in similar contexts. Understanding the difference in what each one does will be a big help when you're trying to figure out which one you need.
Setting up a VM
After spending some time researching virtualisation options on MacOS, including this one which looks interesting but I couldn't quite figure out, I finally found a fantastic guide from Catherine Pope, who explains how to use Universal Turing Machine. The guide is very helpful, and worth a read. It's short and to the point.
To summarise, you need to download UTM, install it from the DMG file, and create a new VM from the menu. The menu is very intuitive and the defaults can usually be accepted with very little modification. I set my VM to only use 32GBs of storage as I didn't have much free space on my machine, and deleted a VM before creating a new one (I don't know if they both consume the allocated storage if it's not used, but I wasn't using these VMs for long anyway).
As for where to get a lightweight OS image from, I decided I could use something very lightweight. I don't need a desktop environment, as I already have a perfectly functional one on the Mac. What I'm doing is kernel development, which will all happen in the terminal anyway. So I looked up Debian, as this will tend to be smaller and less resource intensive than Ubuntu Server (as far as I understand), and interestingly, the first result was from the Libre Debian project. Based on their description, they have forked the Debian source and continued to create an OS that completely avoids non-free software. I think that sort of work is worth knowing about as it is a very valuable service to the rest of us.
I used their standard Debian image, but the slim one might have worked as well. I suspect the slim image might leave out some of the development tools I would need for the projects I was working on, and standard worked just fine on the VM.
Now, the bit that Catherine Pope doesn't mention in her guide, and which I ended up needing to research, is that when you just choose virtualisation and run a VM on an M1, M2, etc chip on MacOS, you will need to select an ARM version of Linux, and will therefore have ARM architecture on your OS.
That might sound obvious, but it wasn't totally clear to me when I started. And the kernel development steps are often totally different, depending on the architecture you're working on, which might mean that some of the guides online are totally unhelpful, as a lot of them target Intel chips.
To solve this, I instead needed to create a VM which uses emulation instead of virtualisation. That's the first step you have to choose in UTM when making a VM. Then you get to choose which hardware you want to emulate, and the type of OS you will be running on it. You can then choose a Linux image that matches that hardware. The Libre Debian images only support 64-bit, so you would need to find a different OS if you wanted to test whatever you were working on on 32-bit architecture (which is the same as x86 or i386 chips for those who don't know).
When you do use emulation for Debian, I noticed that the AMD64 image (which is 64-bit), had basically no installation steps, and took me straight to a logged in shell as a default user. Fortunately this user has sudo privileges. If you set up a VM and encounter any issues with users and privileges, I recommend learning how to use su to log in as root, how to edit the sudoers file to ensure that the sudo group has the required access, and how to create a user and add it to this group, so that you can use sudo to execute privileged commands. If you are setting up Linux VMs for development work, chances are you already know how to do these things, but if you don't, they are very useful skills to have.
Once you have set that VM up, you are now running a Linux VM on MacOS with the hardware of your choice. It won't run as fast as running Linux natively on its own dedicated hardware, or using virtualisation, but it will allow you to choose the environment without needing a different machine for every device type.
Conclusion
My hope is that this clears up some confusion that I personally struggled with for a while when I was using VMs. Setting up a Linux VM or container is a really useful way to try out projects that you can't do on your host hardware. And it makes breaking things much less stressful.