Containers not only change the way of deploying applications but, they affect the way we develop these apps. Thanks to Docker, we can run a variety of services on our local machines. No need to worry about installing extra apps, libraries, and other dependencies.
Docker for Mac, however, is much slower than it's on Linux. Docker server uses lots of Linux kernel-specific features, so it's hard to port it to systems like macOS or Windows. Today's implementation of a docker server based on virtual machines comes with poor performance if it is not tuned for specific OS.
I'm going to show you how to improve the performance of the application running on Docker For Mac by using NFS volumes.
Why volumes on macOS work so slow?
According to the documentation:
There are a number of issues with the performance of directories bind-mounted with osxfs. In particular, writes of small blocks, and traversals of large directories are currently slow. Additionally, containers that perform large numbers of directory operations, such as repeated scans of large directory trees, may suffer from poor performance.
Projects based on Symfony or Laravel Framework consist of lots of files. Every piece of code and responsibility has dedicated classes. Moreover, they need extra libraries that are managed by the composer. Docker needs to keep in sync tons of files between the host machine and the container.
Tweak a configuration to get a better performance
I use separate containers for web server and PHP interpreter. Thanks to this approach, I can use a different version of PHP to test if my application works without touching the webserver.
Unfortunately, my app works so slow, that I have to wait for a couple of seconds to complete a single request. Moreover, my app is an API for the frontend app, so it has to handle many requests simultaneously. In the outcome, I have to wait for ~20 seconds before the frontend app is usable.
Delegated option to the rescue
The full consistency between host and container isn't necessarily needed. From our perspective, we want to see our local changes as fast as possible on the container. If something changes in the container (e.g. app writes logs to the file), we can wait a bit because it doesn't seem to be crucial.
Restart containers and test if something changed. In most cases, the results should be noticeable and sufficient. If you want to achieve a better performance, you may try to mount volumes using NFS.
Setup NFS and mount volumes
Before Docker For Mac, I used to use a docker-machine to run docker server on macOS. It's been using a virtual machine (VirtualBox) under-the-hood so it wasn't so performant as well. However, I found a tool called docker-machine-nfs that mounts the filesystem to the VM by NFS. It increased the overall performance dramatically.
The docker-compose allows defining the NFS volume that can be mounted to the container. The only thing you need to do is to configure and expose your NFS server.
Prepare your filesystem
One of the approaches is to create a separate volume for work data. Thanks to the APFS file system, this operation is easy and non-invasive for the disk. Moreover, you can encrypt the new volume by the independent password to secure your sensitive, work-related data.
This step is optional. If you want, you can export your entire user directory.
Open the Disk Utility tool. Then, add a new volume. There's no need to choose the size of the volume unless you want to reserve or limit the accessible space.
As I said before, I recommend using an encrypted APFS volume. It may reduce the risk of data theft. Of course, you should remember about unmounting volume when you finish work.
Last thing: move your projects to this new volume.
This script removes your project and associated volumes (make sure you have all important data outside volumes), quits docker and configures the NFS server. Please take a look at line XX – it contains a path to the newly created volume.
The whole line should look like:
Check if NFS works properly
You should see the content of your volume. In my case, I have a Projects directory.
Use NFS volume in docker-compose
Now, you can start containers and see if everything is working correctly.
I haven't done exhaustive tests to check how much the performance increased after these changes. Instead, I took into account my own requirements. The application I'm working on has to be fast enough to give me an outcome in seconds. It's subjective, but I don't need to have a production-like performance on my local machine – it's only nice-to-have.
I did a couple of simple tests, where I randomly refresh one view of the React app. It uses API exposed from a container running on Docker for Mac. The React app is running on the host machine.
First, please take a look at the network panel in the browser to see how long I waited for the completion of requests.
Default volume (consistent)
Using delegated option
There is a comparison, how long I had to wait before views were loaded in the browser on a specific volume configuration.
Using NFS may speed up application even more, but it's not a panacea for docker-related performance issues in all projects. I should perform more tests to check if the NFS-powered volumes are suitable for big apps. Or maybe you have some experience with it?