Learn how to set up a local development environment on M-series Macs using Vagrant and UTM, overcoming VirtualBox limitations and enabling seamless virtualization, containerization, and internetworking. This guide provides a step-by-step approach to integrating UTM with Vagrant for a production-like setup.
rev: Spandan Ghosh
The Importance of Local Development
Setting up a local development environment is crucial for any serious developer. It allows you to create a production-like setup right on your machine, often without needing an internet connection. We call this Airplane Mode Development here at One2N, and it's a game-changer for shipping code faster.
Enter Vagrant
Vagrant, from Hashicorp, has been a trusted tool for over a decade, helping engineers spin up quick, portable, and reproducible virtual machines. It's declarative, ensuring consistency in provisioning resources, and it's super fast. You can start up, suspend, resume, and destroy your workloads in just a few seconds.
The Challenge with Apple Silicon
For the problem we are trying to address, Let’s consider our primary development device to be a Mac with M-Series Apple Silicon (I am running an M3). Vagrant provides support for major Type-2 hypervisors in the market (Hyper-V, VirtualBox, VMware Fusion and Docker). However,
Two major blockers arise, when you start setting things up on localhost on it.
Vagrant relies majorly on VirtualBox (by popular choice).
VirtualBox officially no longer supports M-series apple products.
We’ve personally observed VMs crashing and getting stuck in boot phases while trying to work our way with a Vagrant + Virtualbox combination on a M3 device. So we set out on the hunt, for a Mac friendly or native hypervisor to run a decent local development setup on their machine, and expect it to serve the bare minimum capabilities - virtualization, containerisation, persistence and internetworking.
Hypervisors Supported by Vagrant Officially - and Observations + Decisions
Enter UTM: A Native Hypervisor for MacOS
UTM is a 3rd Party - MacOS native QEMU/hypervisor that leverages Apple's Hypervisor virtualization framework. It allows you to run ARM64 operating systems on Apple Silicon at near-native speeds and also supports lower performance emulation for running x86/x64 on Apple Silicon and ARM64 on Intel Macs
Integrating UTM with Vagrant
While UTM is not officially supported by Vagrant, a community-driven plugin has been developed to bridge this gap. Naveen Raj has created the vagrant_utm
plugin, which enables Vagrant to control, provision, and destroy VMs using UTM's APIs. This plugin is a crucial step in making UTM work seamlessly with Vagrant
Setting Up Your Environment
Here’s a step-by-step guide to get you started:
Step 1: Install Vagrant and UTM
First, ensure you have Vagrant and UTM installed on your Mac.
Step 2 - Clone my Github Repository - It’s more than just a Hello World App!
The application is a realistic setup, of perhaps what your local application and dependency stack looks like,
We have a Flask Application, Nginx Routing, Docker Containers, MySQL Container and so on as you’ve observed in the above diagram.
Step 3: Set Up Environment Variables
Create an environment file for your MySQL and Flask API.
NOTE - Remember to rename your env file from (.env -> env) so that UTM can mount it as a visible file.
Step 4: Run Vagrant
Bring up your Vagrant environment.
NOTE - When you do this, UTM will raise a pop-up, and your terminal will ask for permissions for a y/N situation. - Approve the download of the Ubuntu-VM image into UTM and wait for the download to complete. - Once completed, manually mount the project folder in UTM’s "Shared Directory" section. - Remember to rename your env file from (.env -> env) so that UTM can mount it as a visible file.
If you follow this sequentially, you'll see the following trail -
Access Your VM
Once the VM is up, you can log into it using:
Deploying an Experimental Application using Vagrant and UTM
Demo of Vagrant running on Apple Silicon Macs via UTM
This approach establishes UTM as a dependable hypervisor for M-series devices by demonstrating its capabilities in the following technical domains:
Containerization within a Virtualized Environment: Running a Docker Compose setup inside the VM without issues.
Advanced Internetworking: Enabling seamless network communication between the host (laptop), the UTM virtual machine, and Docker networks operating within the VM.
Networked File System Integration: Supporting shared directory mounts on the VM, leveraging workarounds where necessary for compatibility.
Gotchas to Watch Out For When Using This Vagrant File:
UTM Pop-Up and Permission Prompt
Don’t miss the UTM pop-up to confirm the VM setup.
Your terminal will prompt for a
y/N
confirmation to download the VM image. Sayy
or the setup will halt.
Downloading the VM Image
Ensure a stable internet connection and sufficient disk space. A failed download means starting over.
Manual Steps in UTM
After the VM image downloads, manually mount the project folder in UTM’s "Shared Directory" section.
Set the sharing mode to "virtFS" for smooth file access, or your files won’t appear.
If the VM doesn’t boot right away, check and configure boot options or firmware manually, especially for non-standard setups.
Hidden Files Aren’t Your Friend
UTM skips hidden files during mounting. Rename
.env
toenv
so the VM can see it.
Port Forwarding Traps
Ensure port 8080 on your host is free. If it’s in use, NGINX forwarding will fail.
Verify everything works by testing
http://localhost:8080
once the VM is up.
Vagrant File used in Setup
We are not the first!
The issue about Virtualbox withdrawing support for Apple Silicon has been highlighted in multiple forums as early as 2021, and the sources below are compiled. Going through these, should most definitely get you up and running.
Articles
1st article - Linux VMs on an M1-based Mac with VScode and UTM
2nd article - Finding a working VM alternative when VirtualBox no longer works
Discussion Threads
Issue raised here - https://kodekloud.com/community/t/utm-with-vagrant/461691/8
Hackernews (2022) - https://news.ycombinator.com/item?id=33157801
Solution proposed in GitHub Issue - https://github.com/utmapp/UTM/issues/3718