Docker on MacOS with Multipass, for free, the easy way, without Homebrew

Chris Cunningham
Qodea Google Cloud Tech Blog
4 min readJul 13, 2023
Gets you everywhere. (© Columbia Pictures)

A lot of developers use MacBook Pros because they offer a good halfway house between the ease of third-party software installation of Windows and the power of Real Unix (TM) while having excellent hardware with tight OS consistency. It doesn’t hurt that these days Apple Silicon is monstrously fast and has great battery life. But sometimes life is a little bit more difficult when it comes to Linux-specific development features, and one of those areas is Docker.

With Docker Desktop having changed its licensing so that SMEs need to pay per-seat, and with cloud developers generally preferring open source tooling that they can fix or enhance themselves, people have been scrambling for a drop-in solution that allows them to continue to run their Docker images locally with a native experience but without any fear of passing accountants. Rancher Desktop is a common choice here but we’ve encountered some tricky bugs, especially in devcontainer permissions, and it’s quite heavyweight in terms of interface for the use case.

Finally, while a lot of MacOS-bound developers swear by Homebrew as the “missing package manager” that gives them the ability to trivially install dev tooling, the lack of the sort of rigorous oversight present in e.g. the Debian ecosystem along with historical stability issues (not to mention that Homebrew packages are often extremely complicated and difficult to grok when changes are needed) means we’d like to avoid it if possible.

So we’re looking at the following list of requirements:

  1. Able to run Docker locally, using native binaries
  2. A seamless experience running devcontainers using the Remote-Containers workflow, with no need for a two-step process of first remoting into a host machine (Remote-SSH)
  3. An unobtrusive UI
  4. Free and open source, all the way down
  5. Without resorting to Homebrew

Enter Multipass. This is a virtualisation orchestrator created by Canonical ostensibly to provide an easy way to set up a cloud development environment on any platform, with the handy (for Canonical) side-effect of getting Ubuntu on more developers’ desktops. But there’s nothing forcing you from using it to deploy other images, and it comes with a native CLI which lets us script up its deployment pretty easily in shell (as opposed to the likes of UTM which only provides an AppleScript interface).

So what does this look like?

First ensure you have an SSH key in $HOME/.ssh/id_ed25519 and that your $PATH contains $HOME/.local/bin.

Now install the Multipass package:

curl -o multipass.pkg -SL https://github.com/canonical/multipass/releases/download/v1.12.1/multipass-1.12.1+mac-Darwin.pkg
sudo installer -pkg multipass.pkg -target /
rm multipass.pkg

Install the native MacOS Docker binaries:

CPU=$(uname -m)
case $CPU in
arm64) CPU=aarch64 ;;
esac

curl -o docker.tgz -SL https://download.docker.com/mac/static/stable/$CPU/docker-20.10.24.tgz
tar xzvf docker.tgz
install docker/docker ~/.local/bin/
install docker/cli-plugins/docker-buildx ~/.local/bin/
(
cd ~/.docker/cli-plugins
ln -s ~/.local/bin/docker-buildx
)
rm -rf docker.tgz docker/

We can now launch a preconfigured Docker VM inside Multipass. We’ll then mount our MacOS user directory inside the VM, and set it as the primary VM (so that multipass commands infer this to be the machine to run on):

multipass launch docker
multipass set client.primary-name=docker
multipass stop
multipass mount --type native "$HOME" docker
multipass start

Docker will run in the Multipass VM, and the Docker client will connect to the service over SSH. To configure this, first add your SSH key to the instance:

cat ~/.ssh/id_ed25519.pub | multipass exec docker -- bash -c 'cat -- >> ~/.ssh/authorized_keys'

Right now multipass does not automatically configure local hostnames, so we’ll add a snippet to our SSH configuration providing a consistent host to connect to:

cat <<EOF >>~/.ssh/config
Host docker.local
User ubuntu
ProxyCommand bash -c "nc $(multipass ls --format csv | grep docker | cut -d ',' -f 3) %p"
EOF

Finally, add a Docker context which points to the VM and select it. This means that Docker will use this by default.

docker context create ssh --docker "host=ssh://docker.local"
docker context use ssh

We’re all done! To use this, just clone a repository in Visual Studio Code that contains a .devcontainer.json and you’ll automatically be prompted to re-open it in a container. Changes made inside the container will persist outside and vice versa, but otherwise it runs in an isolated environment where dependencies can be installed and tracked independently in code.

To simplify all of the above, we’ve packaged the steps up in an installer geared towards day-1 deployment on a new developer’s machine. This handles all of the key generation and environment setup and so can also be used to bootstrap new joiners into the rest of their workstation configuration. Pull requests welcome!

About CTS

CTS is the largest dedicated Google Cloud practice in Europe and one of the world’s leading Google Cloud experts, winning 2020 Google Partner of the Year Awards for both Workspace and GCP.

We offer a unique full stack Google Cloud solution for businesses, encompassing cloud migration and infrastructure modernisation. Our data practice focuses on analysis and visualisation, providing industry specific solutions for; Retail, Financial Services, Media and Entertainment.

We’re building talented teams ready to change the world using Google technologies. So if you’re passionate, curious and keen to get stuck in — take a look at our Careers Page and join us for the ride!

--

--