Tuning for Docker on Mac

Install Ubuntu 😉

Seriously: In a project all developers using a Mac had poor performance compared to those using a Ubuntu Linux system. While Docker per se is a Linux technology and obviously runs best on Linux the runtimes on Mac were way too slow to be acceptable. So we began research to optimize performance on Mac and ease our daily work.

During research i came across an article about tuning disk performance for Docker on Mac. All proposed steps seemed reasonable accept updating Docker to ‚edge‘ – this seems not necessary any more as the software is matured. Further insight gave an article about using NFS which we didn’t pursue.

First, we defined a disk heavy test task in our project to run on a container to measure runtimes for later comparison.

> time bin/console build
real    41m34.257s

For comparison: The same task ran in 4 minute 25 seconds on a Linux system! That’s 89% faster!

> time bin/console build
real    4m25s

1. Check disk image format

If you upgraded your Mac from an earlier MacOS version like Sierra or El Capitan to High Sierra or Mojave Docker might still be using its old disk format. If you’re still on .qcow2 then upgrade to .raw if system disk filesystem is APFS. Unfortunately this is easiest done by resetting Docker to factory defaults which deletes all images so all container images have to be downloaded and built again.

Check for disk image format: we want .raw for performance on APFS.

After resetting we build our images and measure 19% of speedup:

> time bin/console build
real    33m48.665s

2. Use :cached: for volumes

Simply add :cached: to volumes with many files/much disk I/O. Use an override file so your platform specific changes don’t have to be put in project wide docker-compose.yml. So here is a snippet from docker-compose.override.yml:

version: '3'
services:
  nginx:
    volumes:
      - .:/var/www/html:cached

We measure and achieve about of 44% of speedup:

> time bin/console build
real    18m57.775s
# a newer machine is even faster
> time bin/console build
real    16m20.238s

So all in we achieved 54% faster runtime for our test task! Unfortunately i didn’t find further improvements so we checked another approach.

3. Use Docker with Vagrant

We create a Vagrant machine based on Ubuntu 18.04 to run Docker. Vagrant’s provisioning is used to install latest Docker version. Key is to use NFS for good I/O performance. The Vagrantfile looks as follows:

Vagrant.configure("2") do |config|
    config.vm.box = "ubuntu/bionic64"

    config.vm.network :private_network, ip: "192.168.111.16"

    config.vm.provider :virtualbox do |v|
        v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
        v.customize ["modifyvm", :id, "--memory", 2576]
        v.customize ["modifyvm", :id, "--name", "dockerdev"]
        v.customize ["modifyvm", :id, "--cpus", 2]
    end

    config.vm.synced_folder "../", "/vagrant", :type => "nfs", mount_options:['nolock,vers=3,udp,noatime,actimeo=1']
    config.vm.provision "shell", inline: <<-SHELL
      sudo apt-get update && sudo apt-get install \
        apt-transport-https \
        ca-certificates \
        curl \
        software-properties-common
      curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
      sudo apt-key fingerprint 0EBFCD88
      sudo add-apt-repository \
        "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
        $(lsb_release -cs) \
        stable"
      sudo apt-get update && sudo apt-get -y install docker-ce docker-compose
      sudo usermod -aG docker vagrant
    SHELL
    # config.vm.provision :shell, path: "bootstrap.sh"
    config.ssh.forward_agent = true
end

After creating the box with vagrant up && vagrant ssh we start our Docker services. The newer machine reaches a pretty good time:

> time bin/console build
real	6m3.552s

So this is a pretty impressive improvement – all in all 85,5% – which allows working with Docker on a Mac.

Conclusion

Surprisingly Docker in a VirtualBox managend by Vagrant is faster than Docker for Mac!

This solution might be worth a try: https://github.com/davidalger/warden Under the hood it uses mutagen instead of NFS – don’t know, how that performs.