Speeding up your ocaml/dune build in Docker for Mac

I spent a long time today speeding up my build, and I wanted to share my findings. The two biggest changes:

  • We use docker and share the application directory between the host and container. Docker for Mac has an extremely bad performance problem here which is widely discussed, as docker wants to make sure a shared directory is entirely consistent between host and container. This slows all the file reads and writes that occurs in Dune. I observed somewhere between a 3x - 5x speed improvement by solving this. A lot of complicated approaches exist such as docker-sync, or building in a separate directory and syncing it with unison, but I found the simplest solution was mounting a volume to replace my _build directory so that it was no longer shared.

  • For an unknown reason, when I gave Docker on Mac 8 CPUs, it was much slower than 4 CPUs (unaffected by dune’s -j flag). I dropped from 8 CPUs to 4 CPUs (I tried all other ranges) for a 40% speed improvement. I also found it did better with 2GB of RAM than with 8GB of RAM, though I wasn’t scientific enough to be sure about that.

Hoep that’s useful for other docker/ocaml/dune users!


docker wants to make sure a shared directory is entirely consistent between host and container

That is the default, but I believe you can change it with a mount option:

Yes, I tried both cached and delegated. They both had a minor effect. My compiles went from 20s to 16-17s with cached and delegated, and to 3-4s with a non-shared _build directory. The downside is the build directory is cleared each restart, which is still faster for me.

How exactly do you make the _build/ directory non-shared? I was assuming that some code repository is mounted into Docker like this:

$ cd src/my-project
$ docker run --delete -itv $PWD:/mnt ocaml/opam:latest

Now $PWD would include _build and it would be shared.

1 Like

This worked for me. It first mounts the shared directory, then additionally mounts a volume, which is not shared, in the _build directory, overwriting the existing _build directory.

docker run --mount type=bind,src=$PWD,dest=/home/user/app -v /home/user/app/_build