Docker for Mac used to use osxfuse for file sharing, which has both been slow and caused excessive CPU usage.
The :cached and :delegated option made things a little bit better, but it was still quite bad if you were syncing large directories.
In May 2020, the Docker team added "Improve Mac File system performance" to their roadmap and started experimenting with replacing osxfs with mutagen.
Which actually worked great, but it required synchronization running in the background, which adds complexity, and in the end it was not considered stable enough, or something like that, and dropped in favour of gRPC FUSE –
which is said to solve the CPU usage problem, but which generally seems to be slower than the already slow osxfuse 😱
Mutagen was added to the Edge channel in May (2.3.1.0), and further improved upon until 2.3.4.0.
Testing with 2.3.4.0, it gives near-native performance: (even though the timing doesn't include the time the background sync takes to catch up)
$ docker run -it -v /private/tmp/www2:/var/www:delegated alpine time dd if=/dev/zero of=/var/www/test.dat bs=1024 count=100000
100000+0 records in
100000+0 records out
real 0m 0.47s
user 0m 0.08s
sys 0m 0.35s
The new default in 2.4.0.0 is really quite slow:
$ docker run -it -v /private/tmp/www2:/var/www:delegated alpine time dd if=/dev/zero of=/var/www/test.dat bs=1024 count=100000
100000+0 records in
100000+0 records out
real 1m 12.33s
user 0m 0.67s
sys 0m 4.86s
Disabling gRPC FUSE disabled in 2.4.0.0 takes us back to osxfuse:
$ docker run -it -v /private/tmp/www2:/var/www:delegated alpine time dd if=/dev/zero of=/var/www/test.dat bs=1024 count=100000
100000+0 records in
100000+0 records out
real 0m 28.36s
user 0m 0.45s
sys 0m 2.96s
The Docker team didn't consider NFS an option because it doesn't support filesystem events (inotify). But if you don't need that, it could still be a good option. Having added
/System/Volumes/Data/private/tmp -alldirs localhost
to etc/exports, we can run
$ mkdir /private/tmp/www-nfs
$ docker volume create --driver local --opt "type=nfs" --opt "device=:/System/Volumes/Data/private/tmp/www-nfs" --opt "o=addr=host.docker.internal,rw,nolock,hard,nointr,nfsvers=3" nfstmp
$ time docker run -it -u $(id -u) -v nfstmp:/var/www alpine time dd if=/dev/zero of=/var/www/test.dat bs=1024 count=100000
100000+0 records in
100000+0 records out
real 0m4.840s
user 0m0.170s
sys 0m0.210s
NFS seems like a good middle ground between performance and stability/complexity.
Alternatively, Mutagen can be used standalone, but it requires some setup for each project. Mutagen Compose looks promising, in that it abstracts away much of the added complexity, and comes with built-in support for user mapping. It's still in active development, so could be interesting to monitor and perhaps get back to in a few months time to see if it has caught on.