Okay, if you are reading this, you probably already know that Docker (and not just Docker Desktop) on a MacOS can be awfully slow, especially for developers when the container is accessing files on the local drive via a volume mount. For the most part, this has not really been noticeable with the applications I develop against and so I have been happy to use the defaults. More recently though I have been working on a PHP application and due to its nature it required that "nearly" the whole development space needed to be mapped to a volume and the impact was very apparent even when using the gRPC FUSE option.
Starting point
Using a fairly standard delegated mapping like the one below, we found that the average time to open our home page locally was ~600ms (with the gRPC fuse setting)
version: '3.5'
services:
app:
volumes:
- ./:/var/www/:delegated
Though not the simplest page it wasn't the most complex we have to deal with, can we do better?
Using NFS Mounts
Now a common approach to addressing this issue has been to use an NFS mount instead, setting it up is quite complicated but thankfully it is well documented and scripts are readily available for us to use.
version: '3.5'
services:
app:
volumes:
- nfsmount:/var/www/
volumes:
nfsmount:
driver: local
driver_opts:
type: nfs
o: addr=host.docker.internal,rw,nolock,hard,nointr,nfsvers=3
device: ":${PWD}"
Switching to the above config for mounting the file system and running the same home page test reduced our average load time to ~290ms. This is a pretty good improvement but can we do better?
Using Mutagen
Mutagen is a file synchronization utility that can be used with containers and more recently, with the beta version at least, it can be configured using docker-compose
files.
Installing
Installing is quite easy, though it may take some time; there may be a faster approach but I just followed the hints from homebrew.
xcode-select --install
brew install mutagen-io/mutagen/mutagen-compose-beta
mutagen daemon start
Configuring
Since we are using the beta we can define the mounts in our docker-compose
files.
version: '3.5'
services:
app:
volumes:
- code:/var/www/
volumes:
code:
x-mutagen:
sync:
defaults:
ignore:
vcs: true
paths:
- .DS_Store
- .git
- .idea
- .vscode
permissions:
defaultFileMode: 0644
defaultDirectoryMode: 0755
code:
alpha: "."
beta: "volume://code"
mode: "two-way-resolved"
Note: the permissions section was required as mutagen applies the default permissions of 0600 and 0700 for files and directories respectively however I require the permissions to be 0644 and 0755 so that I could launch the web application (nginx).
Finally, we need to use mutagen-compose
instead of docker-compose
on the command line (or in scripts). It takes a little longer to start due to the initial build of the docker volume; this can be tweaked by being more selective on what files need to be synchronized between the host and the docker volume.
Running the same test again, against the home page, we managed to reduce the average load time to ~160ms.
Summary
All of the above tests were done for a developer build, performance is much better for production builds which are hosted in the cloud.
method
load time (ms)
default
~600
nfsmount
~290
mutagen
~160
It seems that mutagen is definitely a candidate to mitigate some of the performance issues encountered with Docker on the MacOS and should be considered alongside other utilities like docker-sync (requires ruby) and docker-bg-sync.
As always, I hope you find this useful and your feedback is appreciated.