· #posobota s03e03 · online · given in Czech

Ultra-fast Docker on a Mac with an Ubuntu VM

Ultrarychlý Docker na Macu pomocí Ubuntu VM. In early 2021, Docker Desktop for Mac made our Symfony app painfully slow. Moving the Docker engine into a plain Ubuntu virtual machine took one page from 160 seconds to 85 milliseconds.

Update, 2026The file sharing problem behind this talk is much smaller today. Docker Desktop added VirtioFS file sharing, which uses Apple’s Virtualization framework, and made it the default on macOS 12.5+ in version 4.23 (2023). Measure your project on a current Docker Desktop before you build a VM setup like the one below.

The problem: Docker Desktop for Mac

Docker needs a Linux kernel, so on a Mac, Docker Desktop runs it inside a hidden virtual machine. Your project source lives on the Mac and gets mounted into the containers as a volume. Every file the app reads crosses the boundary between macOS and that VM, and back then that crossing was slow.

A PHP app built on Symfony reads a lot of files: vendor code, cache, config. At Carvago it showed. This is our API documentation page, served through Docker Desktop, with the Symfony profiler toolbar at the bottom:

Screenshot of a Swagger UI page for /api/doc/sourcing.json with the Symfony profiler toolbar showing the api_doc_ui route took 160580 ms and 34.0 MiB of memory.
160,580 ms. Not a typo: almost three minutes for one page.

Another load of the same page showed 6,282 ms. Better, still unusable for day-to-day development. The slide after that was the “This is fine” dog sitting in a burning room.

Possible fixes

People tried many things to make this bearable:

I went with the last one.

Remote Docker host

The docker command on your Mac is only a client. It talks to the Docker engine through an API, and that engine doesn’t have to run on the same machine. You keep the local Docker binaries and point them at a remote engine, for example over SSH:

$ export DOCKER_HOST=ssh://sammy@your_server_ip

From then on every docker command runs against that engine. The remote host can be a VPS somewhere, or a local virtual machine on the Mac itself. I chose the local VM.

The key difference is where the files live. In the VM setup, the project source is synced from the Mac into the Ubuntu VM, and the containers mount it from the VM’s own disk. Reads are local Linux file system reads again.

Diagram: inside the Mac, the project source is synced into an Ubuntu VM. Inside the VM, a second copy of the project source is mounted as a volume into Docker.
The project source is synced into the VM once, and Docker mounts it from there.

Which virtual machine?

The usual options are Parallels, VMware and VirtualBox. I showed another one: vftool by Matt Evans, a minimalist command-line wrapper around the Virtualization.framework that Apple added in macOS Big Sur. It runs Linux VMs with virtio block, network, entropy and console devices, and Apple’s framework does the actual virtualization. In the demo it booted Ubuntu 20.04.2 LTS straight into a login prompt in the terminal.

The setup

The whole recipe has three steps:

  1. Set up the VM: install Ubuntu and Docker in it.
  2. Sync files from the Mac to the VM.
  3. Profit.

The step-by-step VM setup was published on the Carvago development blog on Medium. The result: two terminals, one on the Mac and one in the Ubuntu VM.

And the same API documentation page in the VM:

The same Swagger UI page for /api/doc/sourcing.json, now with the Symfony profiler toolbar showing 85 ms and 2.0 MiB of memory.
85 ms, down from 160,580 ms.

The details of the sync and the VM configuration were shown live, so see the video for the full walkthrough.

All 17 slides
  1. Slide 1: Title: Jan Mikeš, #Posobota 27.03.2021, Docker on Mac in Ubuntu VM, Carvago logo
  2. Slide 2: Docker Desktop for Mac: project source mounted as a volume into Docker
  3. Slide 3: Swagger API docs page taking 160580 ms and 34 MiB to load
  4. Slide 4: “This is fine” dog meme in a burning room
  5. Slide 5: Swagger API docs page now loading in 6282 ms
  6. Slide 6: Possible fixes: Docker sync, cached volumes, Docker machine, TMPFS, stop using Docker, remote Docker host
  7. Slide 7: Remote Docker host: local Docker binaries, remote engine, export DOCKER_HOST=ssh://…
  8. Slide 8: Remote Docker host: VPS or local virtual machine, with a diagram of bind mounts, volumes and tmpfs
  9. Slide 9: Diagram: project source synced from Mac into an Ubuntu VM, mounted as a volume into Docker
  10. Slide 10: Logos of Parallels, VMware and VirtualBox
  11. Slide 11: GitHub repo evansm7/vftool: a minimal wrapper for the macOS Virtualization.framework
  12. Slide 12: Terminal showing an Ubuntu 20.04 VM booting via vftool
  13. Slide 13: Steps: setup VM, sync files from Mac to VM, profit
  14. Slide 14: Setup VM write-up on medium.com/carvago-development
  15. Slide 15: Two terminals side by side: one on the Mac, one in the Ubuntu VM
  16. Slide 16: Swagger API docs page now loading in 85 ms
  17. Slide 17: Homer Simpson asking “Questions?” and Thank you! @honza_mikes, janmikes.cz

Related talks