Speed up Docker for Mac with Mutagen

Marick van Tuil
4 min readMay 17, 2020

--

Docker for Mac has some long-lasting performance issues (ever since the launch in 2016) due to the way volume file sharing works. There are plenty of online topics related to this problem:

So when I read in the official roadmap that they will be improving performance, I literally couldn’t wait!

Why is it so slow?

The root of the problem lies in the OS file system layer between Docker and Mac. On Linux, Docker can mount directories and files directly, whereas on Mac, Docker has to pass the request to Mac (osxfs) for each file read/write.

There are two dimensions to filesystem performance: throughput and latency. Throughput is the rate at which data can be processed. Latency is the time it takes for a file system call (e.g. write to that file) to complete.

Throughput is not the problem. Modern systems with SSDs can achieve throughput up to at least a few GBs/second. OSXFS has a limit throughput of 250 MB/s. While this is significantly slower than the throughput of a SSD, this typically is not a bottleneck as most applications don’t read or write that much data each second.

That leaves us with latency. Most block-based filesystems (block-based meaning the data on the disk is stored in blocks) have latency of around 10μs (microseconds). OSXFS is about 13x slower (130μs). While still incredibly fast, when put to the test in a typical web app workload, like npm install, it all adds up and becomes incredibly slow.

Mutagen to the rescue

According to the roadmap, this issue will be solved using Mutagen. Simply said, Mutagen is a file-synchronisation tool. It’s actually quite a nice tool, it allows to synchronise files through SSH, locally, tunnels (peer-to-peer) and… wait for it… Docker containers! And the performance graph looks promising:

Docker-sync comes close too, which works in a similar fashion as Mutagen.

How to use Mutagen in a PHP Docker project

Let’s consider a basic PHP application with a simple docker-compose.yml file, which may look like this:

Presumably already clear to you, but the volume line 7 is what makes our app slow. Let’s see how this would look using Mutagen.

First things first, you will need to install Mutagen on your Mac. Homebrew users (on both macOS and Linux®) can install Mutagen using the following command:

brew install mutagen-io/mutagen/mutagen

Next, start the Mutagen daemon

mutagen daemon start

Using the sync command we may create syncs or list existing syncs. By default, we don’t have any syncs:

mutagen sync list----------------------------------------- 
No sessions found
-----------------------------------------

Cool, that means we can create our first sync. But before we do, we should delete this volume mount in our docker-compose file:

volumes:
- .:/var/www/html

Now we can create the sync. The command looks like this:

mutagen sync create \
--name=<name of sync> \
/local/path/to/code docker://<container user>@<container name>/container/path/to/code

Modify this command as you wish. For me it looks like this:

mutagen sync create \
--name=my-app-code \
~/Code/my-app docker://root@my-app/var/www/html

That’s it! Ensure the sync was created successfully using the sync list command:

mutagen sync list--------------------------------------------------------------------
Name: my-app-code
Identifier: sync_Tw3yrLThKoaZ7W3hjhMt4Q9XDeOW0uIMnUGaNzV7XOS
Labels: None
Alpha:
URL: /Users/marick/Code/my-app
Connection state: Connected
Beta:
URL: docker://root@my-app/var/www/html
DOCKER_HOST=
DOCKER_TLS_VERIFY=
DOCKER_CERT_PATH=
Connection state: Connected
Status: Watching for changes
--------------------------------------------------------------------

Note that it says Waiting for changes, which means the sync was created successfully and it’s now listening for file changes. On a large project with many node_modules it might take a while for the initial sync and it should say something like Scanning files or Staging files on beta.

You’re done. Your app should be blazing fast now. 😎

In my Laravel PHP app, page/API loads finally dropped below 30 ms and common tasks such as npm install, composer dump-autoload sped up significantly.

Before:

50 seconds! Yikes!

After:

1,8 seconds! What will I do with all this time?

Incorporate Mutagen in a development workflow

Each time you open a Docker project you will need to make sure the Mutagen sync exists. Otherwise there will be no files in the container. That means a simple docker-compose up won’t do. To make this easier, I’ve created a simple bash script for starting and stopping containers which ensures syncs are created/destroyed appropriately. Feel free to use the helper below. There might be easier ways to do this, however this works fine in my typical development workflow.

Final words

Mutagen and docker-sync are great ways to speed up Docker for Mac for development. It should arrive in Docker natively later, hopefully within the year.

--

--