I've written blogs in the past on how to improve Docker filesystem mounting performance, including using the overlay2 filesystem and performance tuning Docker for Mac. While they have drastically increased filesystem throughput and performance, none of them have been a silver bullet in truly solving filesystem performance issues with Docker for Mac.
The single biggest performance improvement Docker for Mac has made in the last couple years is adding the delegated flag for mounting filesystems. The "weak set of guarantees" noted by Docker is really quite sufficient, as there are really no noticeable changes between the default and delegated volume mount types. Working with open source software with over one hundred thousand lines of code (~Magento), I've noticed no issues by choosing to run this type of volume mount.
I'm going to talk into more detail about the Magento filesystem, as this is the system I am most familiar with and it presents a few different issues. That said, this approach should really apply to just about anyone having filesystem performance issues with Docker for Mac volume mounts, due to the way D4M operates as a sort of virtual machine to run on the Docker server daemon.
While this appears to be the most straight-forward way to work with a project on Docker for Mac, it's not performant at all, due to the vast amount of files and folders that are written to and read from. Using the above, page load times were roughly 14 seconds from a stale cache — just about twice as long as requests would be, versus running the application on the host machine without Docker at all. While acceptable for development, this really isn't as efficient as it should be.
Let's talk a bit how development works with Magento. Typically, all code updates and changes are handled in one place: the app folder. This is where local changes go, and really the one true place we need bi-directional sync of folders and files from and to Docker. So, why not just use native Docker volumes, and mount just that app directory?
echo "Completed copying all files from host to container"
echo "Completed copying $1 from host to container"
echo "Completed copying all files from container to host"
echo "Completed copying $1 from container to host"
generated/: This folder contains files and folders that are automatically-generated on demand, that use Magento's "hook/plugin" system. Since this folder can contain hundreds or even thousands of files, and need to be written to and read from on every request, this is an obvious folder to keep within the native Docker filesystem. We also do not need bi-directional sync. The only time we'll need to be able to view these files is during certain times of debugging, in which case we can just use the bin/copyfromcontainer generated command to copy files back to our host for debugging.
pub/: Every single one of our web requests and requests for public access makes a request to this folder. This is most likely our heaviest-requested access point in our entire app. Since it's read heavy, and we aren't ever really editing files within this directory, we just keep this in the native Docker volume.
var/: Here is a very heavily-utilized folder in Magento, as it contains all cache files, session files, log files, etc. This is the true definition of a caching/temp directory. We rarely need access to anything in this file. If we needed access, we can always connect to the container to inspect the files needed, use the bin/copyfromcontainer var command, or mount a specific subfolder we need access to (example: mount var/log to the host for bidirectional sync will only mount the var/log folder, which isn't a heavily written-to or requested-from folder).
vendor/: This is considered an "artifact" directory. Third-party Magento modules get installed here from Composer. It almost certainly contains the highest amount of read from files, and requests hit this directory many, many times on each and every code and app request. Also, we really never edit files here. So, let's keep this just in a native Docker volume. If you did handle development within a subfolder of here instead of the app directory, just mount the namespace of your specific directory (ex: vendor/foo).
What happens after we apply these updates? Our initial requests of 14 seconds drop to around 7 seconds, and we achieve app performance that roughly 90% or better of native speed/performance. All of the main filesystem reads now happen within the native Docker container/volume, and don't get need to loop through our host machine unnecessarily. Doing this is how we can achieve a significant improvement in I/O within our Docker development environment.
I'm hoping this post helps put an end to filesystem performance issues on Docker for Mac. Hopefully one day, we will not even need to do this! But until then, this is a pretty solid solution that works well for the edge-cases for OSS with very large filesystems.
If you wish to check out my Magento Docker development environment in it's entirety and be able to use many other bin bash helper scripts I've created to deal with common issues, check out my GitHub project at markshust/docker-magento.