TL;DR summary

If you are using Docker Toolbox then you may have noticed that data volumes mapped to local directories on the Mac (using the -v option of the docker run command) do not see changes made to the local files. I have developed a workaround using rsync. See the GitHub repo https://github.com/JeNeSuisPasDave/asd-sync-src-to-container for examples of the workaround.

The use case for sharing local files with a Docker container

My main use case, currently, for Docker containers is to test code written to run on Linux servers. My development system is a Mac, still running El Capitan (OS X 10.11). I want to write code using Sublime Text 3 on my Mac, share that code with a Docker container using a data volume, and build and test the code in the container. I want to iterate through many edit-build-test cycles using that setup.

Using Docker Toolbox

On a Mac there are, currently, two choices for hosting Docker containers. The original mechanism was to host a Linux VM using VirtualBox. This environment is well supported with Docker Toolbox—which includes a version of Docker Engine that works on the Mac and also includes Docker Machine, a utility used to create and manage VirtualBox Linux VMs that host a proper Docker environment. The VMs created by Docker Machine use the Boot2Docker distribution, a lightweight Linux system intended specifically to run Docker containers in a development environment.

The VMs created by Docker Machine share the host’s /Users folder with full access. This allows the mapping of any directory or file in the /Users tree to be mapped to a Docker data volume. For example, if I wanted to map my project source code to a /src folder in the container, I could do something like this:

docker run -i -t --rm -v "${PWD}/src:/src" datihein/test /src/build-and-test.sh

In that case I’m mapping my local ~/src directory to a new directory /src in the container. After the container is instantiated, the /src folder will be mounted and the build-and-test.sh script will be executed.

But there is a problem

The problem with this setup is that I can’t do any file watching. That is, once data volume is established then the container won’t see any changes to the host directory. This is a problem if I’m doing web development and want to keep a container-hosted web server up and running while I tweak CSS and JavaScript files on the Mac. If I change those files on the Mac, the web server will still be seeing the old content.

Even if I shut down and remove the container, the next time I run the container it will continue to see the file content that existed the first time I created a data volume mapped to the $PWD/src directory. To get containers seeing any updated content I have to reboot the Docker VM.

I don’t fully understand why this happens, why the Docker VM is apparently caching content from shared host folders and why the cache isn’t being updated. But it has something to do with inotify not being supported by VirtualBox. If an editor changes a file, the new file will often end up with a different inode that the original version. File watching mechanisms listen for inotify events to discover such changes. But VirtualBox does not propogate inotify events on the host to guest VMs (see ticket #10660). Enough people run into this problem (see [issue #15793][15793]) that it seems like Docker should document the issue prominently; it does not.

I have a workaround for the problem (spoiler: it involves rsync), but first let me address the obvious solution.

Using Docker for Mac

It seems obvious that the inotify problem with using VirtualBox to host containers would be elminated by using Docker for Mac. No VMs, no VirtualBox, just native containers.

Unfortunately, there are performance problems with data volumes mapped to host directories. Data access is measured at 10 to 20 times slower than data volume containers. This issue was reported in March 2016 and is still a problem now, in November 2016.

This isn’t the only issue with Docker for Mac. The product remains a beta, and I’ve got enough learning to do on containers to want to avoid adding a beta implementation to the mix. So I’m sticking with a Linux hosted Docker for now, and that means finding a way around the VirtualBox inotify problem.

Solution: A workaround for lack of inotify propogation

The basic idea is to use an intermediate directory on the Docker VM, and use rsync to keep that directory up to date with the latest files and changes of the local directory on the Mac.

Any time you change files on the Mac, you will invoke rsync to push those changes to the intermediate directory hosted on the Docker VM. Any container hosted on the Docker VM and having a transient data volume mapped to the intermediate directory will get the inotify events and will see the changed files. See the sync-to-host-mapped-dv example in the https://github.com/JeNeSuisPasDave/asd-sync-src-to-container repository for example scripts that do just that.

If you have a data volume container, then two synchronization operations are required: the rsync from Mac to Docker VM directory, and then a second rsync to transfer files and changes from the Docker VM intermediate directory to the data volume container. See the sync-to-dv-container example in the https://github.com/JeNeSuisPasDave/asd-sync-src-to-container repository for example scripts that keep a data volume container synchronized with a local directory on the Mac.

In the fully functional examples of the GitHub repo, the run.sh script is used first. It establishes and populates the intermediate directory, establishes and populates the data volume container (if you are using one), and launches the “work” container with attached data volumes.

From that point on you run refresh.sh script anytime you have changed files on the host that you want to propogate to the container.

Taking the solution further

  • You could add invocation of refresh.sh script to your editor (e.g. using a build system configuration in Sublime Text 3).
  • You could automate the execution of refresh.sh so that it is triggered by file system changes on the Mac … using, for example:
    • the Python package watchdog to monitor directories and files for changes, triggering refresh.sh when the changes are detected)
    • Hazel to monitor folders and trigger refresh.sh when changes happen
    • Folder Actions and Automator

Caveat: I’ve not tried any of the above automations (read: don’t ask me for help with them). For the moment I’m happy to just type refresh.sh when needed.