Lima enables you to run a Linux machine on your Mac for free. This tutorial helps you install Lima, launch a Linux VM, verify shared files, and run a web server in a container accessible via your browser. If you want the concepts first, read [What Is Lima?][what-is-lima] and come back. This article stays hands-on. ## What You'll Build A working Lima setup: an Ubuntu virtual machine (VM) running on your Mac, managed from your terminal, serving a containerized nginx web page at `localhost:8080`. What success looks like: you type `curl localhost:8080` on your Mac and get a "Welcome to nginx!" page served from Linux. ## What You'll Learn * How to install Lima and start your first Linux VM. * How to run Linux commands from your Mac terminal. * How file sharing between your Mac and the VM works. * How to run a container with `nerdctl` and reach it from your Mac. **Time estimate:** about 30 minutes, mostly waiting for downloads. **Difficulty:** beginner. ## Prerequisites **You need:** * A Mac with [Homebrew][homebrew] installed. Apple Silicon or Intel both work. * macOS 13 or newer. Lima's fast `vz` driver needs it. Older macOS versions fall back to the slower QEMU driver. * About 5 GB of free disk space for the VM image and container images. * A terminal you're comfortable with. This tutorial covers Lima 2.x on Apple Silicon. Commands have been stable since Lima 1.0, so version drift is unlikely to cause issues. **You don't need:** * Docker, Docker Desktop, or any container experience. * Any virtualization knowledge. Lima handles the VM plumbing. Everything in this tutorial is reversible; one command at the end removes the VM and all contents. ## Setup ### Step 1: Install Lima Install Lima with Homebrew: ```bash brew install lima ``` Confirm it installed: ```bash limactl --version ``` **You should see:** a version string such as `limactl version 2.2.2`. Your version number will differ, and that's fine. **Checkpoint:** If `limactl` prints a version, you're ready. If your shell says `command not found`, jump to Troubleshooting. ## Tutorial Steps ### Step 1: Start your first Linux VM Lima calls each VM an instance. Start the default one: ```bash limactl start --containerd user ``` The `--containerd user` flag matters: since Lima 1.0, the default template leaves out containerd, the container engine you'll need in Step 4. The flag tells Lima to install it, along with the `nerdctl` command. On the first run, it asks how to create the instance. Pick **Proceed with the current configuration** and press Enter. The defaults give you an Ubuntu VM with 4 CPUs, 4 GiB of memory, and a 100 GiB disk that uses space only as needed. The first start downloads an Ubuntu image, so give it a few minutes. **You should see:** log lines about downloading and booting, ending with: ```text READY. Run `lima` to open the shell. ``` **What just happened:** Lima downloaded Ubuntu, created a VM named `default`, booted it, and wired up file sharing and port forwarding. You now have a Linux kernel running on your Mac. **Checkpoint:** Run `limactl list`. The `default` instance shows `Running` in the STATUS column: ```text NAME STATUS SSH VMTYPE ARCH CPUS MEMORY DISK default Running 127.0.0.1:60022 vz aarch64 4 4GiB 100GiB ``` Your SSH port and architecture may differ. `Running` is what matters. ### Step 2: Run Linux commands from your Mac The `lima` command runs whatever you give it inside the VM. Prove you're talking to Linux: ```bash lima uname -a ``` **You should see:** a line starting with `Linux lima-default`, ending in `aarch64 GNU/Linux` on Apple Silicon or `x86_64 GNU/Linux` on Intel. Your Mac would have said `Darwin`. This output comes from the Ubuntu kernel inside the VM. Run `lima` with no arguments to open an interactive shell in the VM, then `exit` to come back to macOS. For the rest of this tutorial, stay on the Mac side and prefix commands with `lima`. **Checkpoint:** `lima whoami` prints your macOS username. Lima creates a matching user inside the guest so file permissions line up. ### Step 3: See file sharing in action Lima mounts your home directory into the VM read-only, and mounts `/tmp/lima` writable in both directions. Test both. Create a file on your Mac: ```bash echo "hello from macOS" > /tmp/lima-test.txt ``` Read it from inside Linux: ```bash lima cat /tmp/lima-test.txt ``` **You should see:** `hello from macOS`. Now go the other way. Write a file from the Linux side into the shared writable directory: ```bash lima sh -c 'echo "hello from Linux" > /tmp/reply.txt' ``` Read it on your Mac: ```bash lima cat /tmp/reply.txt ``` **You should see:** `hello from Linux`. **What just happened:** both systems see the same files with no copying or syncing. Your home directory is read-only inside the guest by default, which protects your Mac from a misbehaving VM. `/tmp/lima` is the default writable exchange spot. **Checkpoint:** If both reads worked, file sharing is healthy. Clean up both test files: ```bash lima rm /tmp/lima-test.txt /tmp/reply.txt ``` ### Step 4: Run a container and reach it from your Mac The `--containerd user` flag from Step 1 gave your instance containerd and `nerdctl`, a command that works like `docker`. Start an nginx web server: ```bash lima nerdctl run -d --name web -p 8080:80 nginx:alpine ``` **You should see:** image download progress, then a long container ID on the last line. Now ask for the page from your Mac: ```bash curl localhost:8080 ``` **You should see:** HTML containing `Welcome to nginx!`. Opening `http://localhost:8080` in your browser shows the same page. **What just happened:** The container runs inside the VM and listens on the VM's port 8080. Lima noticed the port binding and forwarded it to your Mac automatically, so `localhost` works as if the server were native. **Checkpoint:** `lima nerdctl ps` lists the `web` container with a status of `Up`. ## Verification You're done when all three are true: * `limactl list` shows the `default` instance as `Running`. * `lima uname` prints `Linux`. * `curl localhost:8080` returns the nginx welcome page. Then clean up the tutorial container: ```bash lima nerdctl rm -f web ``` When you're not using the VM, stop it to free memory: ```bash limactl stop ``` Start it again anytime with `limactl start`. Your VM and its contents survive stops. To remove Lima entirely, `limactl delete default` deletes the VM and `brew uninstall lima` removes the tool. ## Troubleshooting ### Problem: `limactl: command not found.` **Symptoms:** The shell can't find `limactl` right after installing. **Solution:** Your shell hasn't picked up Homebrew's path. Run `eval "$(brew shellenv)"` or open a new terminal window. **If that doesn't work:** run `brew list lima` to confirm the install succeeded, then `brew doctor` to check your Homebrew setup. ### Problem: the first start hangs or takes forever **Symptoms:** `limactl start` sits on a download for many minutes. **Solution:** The first start pulls an Ubuntu image of several hundred megabytes. On slow connections, this takes a while, and later starts skip the download. Let it finish. **If that doesn't work:** press Ctrl+C, then run `limactl start` again. Downloads resume from cache, and a corrupted download restarts cleanly. ### Problem: `Read-only file system` when writing a file **Symptoms:** writing anywhere under your home directory from inside the VM fails. **Solution:** That's by design. Your home directory mounts read-only for safety. Write to `/tmp/lima` instead, as in Step 3. **If that doesn't work:** when a project needs writable mounts, run `limactl edit default` and set `writable: true` on the mount. Stop and start the instance afterward. ### Problem: `nerdctl: command not found` **Symptoms:** Step 4 fails with `/bin/bash: line 1: nerdctl: command not found`. **Solution:** The instance was created without containerd, which happens when Step 1 runs as plain `limactl start` without the `--containerd user` flag. Enable it on the existing instance: ```bash limactl stop limactl edit default --set '.containerd.user=true' limactl start ``` Lima installs `nerdctl` inside the VM on the next start. Rerun Step 4 afterward. **If that doesn't work:** run `grep -A2 '^containerd:' ~/.lima/default/lima.yaml` on your Mac. If `user` still shows `false`, the edit didn't save; rerun the `limactl edit` command. ### Problem: port 8080 is already taken **Symptoms:** the container starts, but `curl localhost:8080` returns something that isn't nginx, or the run command fails to bind. **Solution:** Another app on your Mac owns 8080. Remove the container with `lima nerdctl rm -f web`, then rerun Step 4 with `-p 8081:80` and curl `localhost:8081`. **If that doesn't work:** `lsof -i :8080` on your Mac shows which process holds the port. ## Next Steps **To learn more:** * Read [What Is Lima?][what-is-lima] for the mental model behind what you just built: the hypervisor, the mounts, and the port forwarder. * Browse the [Lima documentation][lima-docs] for configuration, templates, and drivers. **To extend this project:** * Try another template: `limactl start template://docker` gives you a VM with a real Docker daemon, so the `docker` command works against it. * Give the VM more or less hardware with `limactl edit default`, which opens the instance's YAML configuration. * If you want a drop-in `docker` replacement with one command, look at [Colima][colima], which builds on Lima. ## References * [Lima documentation][lima-docs], the official guide covering installation, templates, mounts, and port forwarding. * [Homebrew][homebrew], the macOS package manager used to install Lima. * [Colima][colima], a Lima-based tool that provides a Docker-compatible runtime. [what-is-lima]: https://jeffbailey.us/what-is-lima/ [lima-docs]: https://lima-vm.io/docs/ [homebrew]: https://brew.sh/ [colima]: https://github.com/abiosoft/colima