Linux Virtual Machines on Mac with Lima
Author
Note: This lesson is specifically for people using Mac-OSX, so they can run a container-compatible Linux environment. If you are using Windows, we recommend you just install WSL (Windows Subsystem for Linux) to get a Linux virtual machine and jump straight to the next lesson. If you already using Linux, then you can also skip this.
In this notebook, you will set up and work with Linux virtual machines on macOS using Lima. The focus is practical: you will build a working environment that supports container-based development, with a particular eye toward using Apptainer in research workflows.
If you regularly work with Python in scientific or research settings, you are likely already dealing with the mismatch between macOS development environments and Linux-based production systems (e.g., HPC clusters). Running a Linux VM locally helps you close that gap and avoid subtle compatibility issues.
Lima gives you a lightweight, scriptable way to manage these environments. As you go through the exercises, you will build a clear mental model of how Lima handles virtual machines, how those machines interact with your filesystem, and how to control their resources and architecture. Each section focuses on a specific capability, with short exercises designed to make behavior visible and testable.
Setup
Install Lima with Homebrew
Lima can be installed with Homebrew with a single command:
brew install limaAlternative installation methods can be found in Lima’s Installation Docs
Section 1: Setting Up and Running Virtual Machines (VMs)
In this section, you will learn the basic lifecycle of a Lima virtual machine: how to create, start, access, list, and delete instances. These operations are foundational—everything else in this notebook depends on being able to do them quickly and reliably.
As you work through the exercises, focus on making the system observable. You will confirm that you are actually running Linux, see how named VMs are tracked, and get comfortable treating VMs as disposable resources. This is an important shift: instead of carefully maintaining a single environment, you can create and discard VMs as needed.
You will also use a predefined template to create a VM with Apptainer already available. This introduces the idea that templates can encode higher-level environments, allowing you to bootstrap more complex setups without manual configuration.
Exercises
For each exercise, enter the sequence of commands one-by-one.
Exercise: Start a default VM, “shell” into it, and confirm its operating system is Linux.
limactl start --name default
limactl shell default
uname -a
exit # or press Ctrl-DExercise: Create a VM for using Apptainer from one of Lima’s provided Templates with template:apptainer. Verify apptainer is available by entering the command apptainer in the VM’s shell.
limactl start --name apptainer template:apptainer
limactl shell apptainer
apptainer
exit # or press Ctrl-DExercise: List the VMs installed through Lima currently with limactl list
limactl listExercise: Delete each of the VMs with limactl delete -f <name> (e.g. limactl delete -f default). Verify the VM is gone from the list afterward with limactl list
limactl delete -f default
limactl delete -f apptainer
limactl listSection 2: Working on the Your Files, from the VM
In this section, you will explore how your VM interacts with files on your host system. By default, Lima exposes parts of your home directory inside the VM, which allows you to keep your code and data on your Mac while executing commands in a Linux environment.
However, this access is intentionally constrained. You will notice that reading files works as expected, but writing to the filesystem may be restricted depending on how the VM is configured. This behavior is designed to protect your host system from unintended changes.
The exercises walk you through this boundary step by step: first observing that your files are visible, then attempting to modify them, and finally adjusting the VM configuration to allow writable access. The goal is to understand both the default safety model and how to change it when your workflow requires it.
Exercises
Example: Create a new lima VM from a working directory under your home directory and list all the contents of the current directory from the VM shell (ls). Does Lima see your files?
limactl start --name default
ls
limactl shell default lsExercise: Run touch newfile.txt from the lima VM’s shell to try and create a new file, and confirm that the VM doesn’t allow the file createion.
limactl shell default touch newfile.txtExercise: Modify the VM by stopping it, then starting a Debian VM with filesystem writes enabled by adding the --mount-writable option when starting it.
limactl start --name default2 --mount-writable
limactl shell default2 touch newfile.txt
lsSection 3: Setting the Computational Resources Available to the VM
In this section, you will control how much CPU and memory your VM can use. While Lima provides reasonable defaults, many workloads—especially those involving containers or data processing—benefit from explicitly defined resource limits.
You will start by inspecting the current resources available inside your VM, then restart it with modified CPU and memory settings. This helps you verify not only how to configure these parameters, but also how those changes appear from within the system.
Being explicit about resource allocation serves two purposes. It helps you avoid overloading your local machine, and it makes your environment more reproducible when shared with others or moved between systems.
Exercise: Create a VM from the apptainer template and check its computational resources: how many processes does it have, and how much memory in RAM is available?
limactl stop default
limactl start --name default --mount-writable template:apptainer
limactl shell default nproc # How many processes are there?
limactl shell default free -h # How much memory is there? Exercise: Restart the VM (stop then start) to include 1 CPU (--cpu 1) and 2GB of memory (--memory 2), and verify that it worked.
limactl stop default
limactl start --name default --mount-writable --cpus 1 --memory 2 template:apptainer
limactl shell default nproc # How many processes are there?
limactl shell default free -h # How much memory is there? Section 4: Running a VM with an Alternative Processing Architecture
In this section, you will run virtual machines with a different CPU architecture than your host system. This is particularly relevant if you are using Apple Silicon, where the native architecture (ARM) does not always match the expectations of existing tools and containers.
Using QEMU through Lima, you can emulate an x86_64 environment. This gives you broader compatibility, but at the cost of significantly-reduced performance. Understanding this tradeoff is important when deciding how to run specific workloads.
You will first check the architecture of a default VM, then create a new VM that emulates x86 and confirm the difference. The goal is to help you recognize when emulation is necessary, and what costs it introduces in practice.
Exercises
Exercise: Install the lima-additional-guestagents package:
brew install lima-additional-guestagentsExercise: Check the architecture in being run by Lima’s default VM with uname -a:
limactl shell default uname -aExercise: Create a new VM that uses QEMU to simulate an x86 architecture, and check its architecture with uname -a
limactl start --name x86 vm-type=qemu --arch=x86_64
limactl shell x86 uname -a