Using OverlayFS for project work with copy-on-write

Sometimes I find myself working on a project (possibly not mine) and wanting to track changes I’m doing in that project. But either the project is not suitable for a full-blown version control system like Git (because I’m not necessarily talking about software development here) or I don’t want to (or can’t) introduce this tool into the workflow.

I was wondering whether I can use the same method that container runtimes are using to separate static container images from ephemeral containers. The answer turns out to be: yes!

Enter OverlayFS: as the name suggest, this “filesystem” allows mounting one filesystem on top of another one and interacting with the merged result. This works not only for entire filesystems, but also for individual directories.

My use case is the simplest possible scenario for an overlay filesystem:

  • the lower layer contains the original work and should remain unchanged at all times
  • the upper layer contains the changes made “to” the original project
  • both layers are merged in a mount point that represents the current work

So far the theory, let’s put this into practice. We’ll need four different directories. At the beginning, workdir and the mount point (merged) should be empty. Note that workdir directory needs to be on the same filesystem as upperdir.

1
sudo mount -t overlay overlay -o "lowerdir=./original,upperdir=./changes,workdir=./work" "./merged"

Now you can see the merged result of lowerdir and upperdir in merged!

As this StackOverflow post points out, the error messages from the overlay driver can very be misleading. More accurate error messages are available through the kernel’s diagnostic messages:

mount: /home/jack/merged: special device overlay does not exist.
$ dmesg
[ 5072.133631] overlayfs: failed to resolve '/home/jack/original': -2

 

mount: /home/jack/merged: wrong fs type, bad option, bad superblock on overlay, missing codepage or helper program, or other error.
$ dmesg
[ 5507.655432] overlayfs: unrecognized mount option "workdir=" or missing value

When we create a new file or modify an existing file in the overlayed filesystem (merged directory), it will appear in the upper layer (changes directory):

$ tree
├── changes
├── merged
│   └── greetings.txt
├── original
│   └── greetings.txt
└── work
    └── work

$ touch merged/hello-world.txt
$ tree
├── changes
│   └── hello-world.txt
├── merged
│   ├── greetings.txt
│   └── hello-world.txt
├── original
│   └── greetings.txt
└── work
    └── work

$ echo "Have a great day!" >> merged/greetings.txt
$ tree
├── changes
│   ├── greetings.txt
│   └── hello-world.txt
├── merged
│   ├── greetings.txt
│   └── hello-world.txt
├── original
│   └── greetings.txt
└── work
    └── work

Since any file changes are automatically copy-on-write, this gives me a great overview of the changes I have made to a particular project with file-level granularity. For more detailed change inspections, I can still fallback to good old diff since I know which files to look at.

The overlay filesystem can be unmounted (sudo unmount ./merged) and later mounted again with the same command as above, since the changes are persisted in the upper layer (changes). Additionally, multiple lower directories can be used: lowerdir:./one:./two:./three. For more details, please refer to the Linux kernel’s documentation on OverlayFS (it’s very accessible).

Happy hacking!