Builds that are reliable and reproducible are a great thing. Containerization technology like Docker is a huge help here, and we use Docker at Rally for many tasks. But not everything lends itself to that.
So as developers, we still spend a decent portion of our daily life outside of containers. We need robust documentation and scripts to make that happen smoothly. But if all our machines are unique and acquire crud and customizations over time, there are bound to be disagreements: along the lines of “that works on my machine”, and “well not on my machine”. How do we agree on anything? The obvious answer is that a fresh OS install on a specific type of machine is our common starting point — it’s a fixed point in time.
That works, but it’s problematic. It’s not cost-effective to have spare developer-grade machines lying around just to test installation procedures, nor is it time-effective to be wiping and rebuilding machines either. The cost only increases if you’re trying to diagnose an issue towards the tail end of a procedure.
Luckily, there’s a solution that’s inexpensive on both the cost and time dimensions: virtualizing macOS on top of your existing machine.
Virtualization isn’t new. What’s a more recent development is being able to easily virtualize macOS without engaging in legally questionable maneuvers. (A clause explicitly permitting virtualization on a Mac host first appears in the SLA of Mac OS X 10.7 Lion.) Many posts online detail these, but they’re out of date and no longer work.
As I write this, 10.13 High Sierra is the newest version of macOS, and 10.12 Sierra is the next most recent. So let’s start there, with virtualizing Sierra on top of High Sierra.
Note that you can’t virtualize OS versions that aren’t natively compatible [E.g., High Sierra on a Late 2008 Macbook, for example]. I also have not successsfully installed a High Sierra guest anywhere. So your mileage may vary.
Getting Set Up
Step 1: Install Parallels Desktop Lite from the Mac App Store. It’s free to download. If you want to be able to run Windows guests, it’s a $60 in-app purchase, but running Linux (or Mac!) guests is free.
Step 2: Download an OS installer. The newest one can be found in the Mac App Store by searching for “install macos”, or by navigating the Categories view to “Utilities > Apps Made By Apple > macOS”.
macOS High Sierra Installer
As seen above, older ones won’t show up in either view, but are still there and can be downloaded via direct links like these:
Those direct links are taken from Apple’s website:
As long as you don’t manually run the installers, downloading them will do you no harm other than taking up disk space. (Which is not nothing - they’re around 5GB each.)
Making a Mac Virtual Machine
Open up Parallels. You should see this screen:
Select “Install Windows or another OS from a DVD or image file”, and click continue.
It will probably find the installer app you downloaded. If it doesn’t for some reason, click on “Locate Manually”, and find it in
/Applications/. Click continue.
Click continue when it asks you if it can create a bootable disk image file. The default location is an ugly slug of something like
~/Library/Group Containers/blahblah.parallels.blah/Shared/Parallels. If you want ever want to find this file again without having to read the help, change it to something more memorable like
Pause for quiet introspection while it writes out several gigs to your disk. Ignore the spinner still trying to locate installation media. Soon you’ll see this screen:
Give it a name and a directory to store the vm file in (
~/VM files/ would be sensible), and click continue.
And boom! We’ve got a virtual Mac and we’re installing the OS just like on a native machine.
Finish the installation of your chosen version of either OS X or macOS. Maybe go make some tea. Too much coffee isn’t good for you.
For our purposes, it’s fine to not enable location services, and to skip signing into an Apple ID. Set up the initial user account just like you would on a native install.
Configuring the Virtual Machine
Once the installation finishes, and you’re logged in, there’s a few steps we need to take before doing anything else.
Install Parallels Tools
If you’ve used VMWare sometime in the past 15 years, this step should be familiar to you. Installing hooks into the guest OS to allow for better integration with the host OS, like shared clipboards and shared folders.
Click the yellow caution sign in the top right corner, and select “Install Parallels Tools”. A disk will mount with an “Install” app in it. Run it.
Remember that when you’re asked for your admin password, it’s your guest machine password, not your host machine one.
When it finishes, restart the VM when prompted. If the “Parallels Tools” disk is still mounted, eject it. We won’t need that anymore. (Again, just like every other VM you’ve used this decade.)
Take a snapshot
Stop. Do not take any further action until we complete this very important step.
Right now we have a disk image hdd file that’s around 5GB, and a virtual machine pvm file that’s around 10GB.
On your host machine, go to the “Action” menu and select “Take Snapshot…”. What we want is to insure ourselves against screwing up this machine by saving its state right now, so that we never have to redo that OS install again.
Done? Good. We’ll come back to snapshots in a bit.
NOTE: With VMWare Workstation, I would often shut down my VM before taking snapshots. Doing so meant it only had to store the state of the VM’s disk. Taking a snapshot with the machine powered on meant it also had to store the contents of memory. While it was smart enough to only store the incremental change with a disk snapshot, it would store the full uncompressed copy of system memory. So snapshots were much larger. In Parallels Lite, this is not an option and snapshots can only be taken with the machine on.
Our disk usage is now at around 12GB for the pvm file and another 5GB for the bootable disk image. Not lightweight, but still less than another machine.
Other Initial Setup
Open Terminal.app and run the following commands to properly name the machine
sudo scutil --set ComputerName deckard sudo scutil --set HostName deckard sudo scutil --set LocalHostName deckard
Do any other initial setup that’s unlikely to need to be changed, like removing all the default dock icons you don’t care about, and setting a nicer desktop background. Take another snapshot.
More on snapshots
Now we can get back to one of my favorite features of using VMs. On your host machine, go to the “Actions” menu and select “Manage Snapshots…”
Snapshots of our VM
We’re happy with the customizations we made, and there’s no longer any value in keeping both of these states saved, so we can delete the “Fresh Install” snapshot. If you check in the Finder, the
Deckard.pvm file is still only 12.01 GB, so creating a snapshot only to remove it later didn’t permanently bloat our VM.
This illustrates a pattern I often use:
- Take a snapshot
- Do some work
- If we’re happy with what happened, take a new snapshot and delete the previous one. If it blew up, revert to the snapshot from 1 and try again.
But wait there’s more!
We can continue iterating this way, creating a chain of snapshots that is arbitrarily long. You can jump back and forth between any saved points to try things out, without altering our pristine snapshots.
It doesn’t have to be a straight line. Snapshots can branch into a tree structure. You can have happy and unhappy install paths, different configurations, or whatever else you need. Disk space on the host is really the only thing that limits you.
Shown here is an actual work in progress for a guide I’m working on for installing & customizing your shell:
VM snapshots for an in-progress shell customization guide
Mac on Mac VMs are also useful for making “clean” screenshots that illustrate a point, but don’t reveal any details about what other documents or applications you have on your machine. For example:
Picking an application to open a reStructuredText document
Containerization is often the best way to improve repeatability and reliability across machines. But it can’t fix everything. Need to check your code against a new version of something you install via Homebrew? Docker can’t help you. It’ll kindly inform you how your code runs on any number of Linux distros. But there’s no
docker pull apple/macos. So in the absence of that, full machine virtualization makes a great substitute.