My typical OCaml development workflow starts with git clone ..., then opam switch create . 5.4.1 --deps-only followed by dune build. This creates a local _opam directory containing the compiler and all dependencies. It works well, but the _opam directories add up, and the build takes time.
Across my projects, the cumulative size of these _opam directories is 88 GB, with individual switches ranging from around 400 MB to over 3 GB. Most of that space is taken up by duplicated packages, rebuilt from scratch for each project.
day10 already solves this problem for testing packages by building dependency layers once and reusing them across packages. The new day10 build subcommand brings the same approach to local development. Instead of creating an opam switch, it assembles a container from cached dependency layers and runs dune build inside it, with the source directory bind-mounted so that _build/ appears on the host.
day10 build .
This is equivalent to opam switch create . --deps-only && dune build, but dependencies are never rebuilt if they already exist in the cache. On a warm cache, the solve takes under a second, and the container assembly is near-instant, so you go straight to your project’s compilation.
On a cold cache, day10 build is slower than opam for a single project because it builds each dependency sequentially, one layer at a time, whereas opam parallelises the installation. The advantage comes when you work on multiple projects: the cached layers are shared, so dependencies compiled for one project are reused by the next.
Because everything runs inside the container, including the compiler, opam, dune, and all dependencies, the host machine does not need to install any OCaml toolchain. The only requirements are day10 itself and runc, which could be made available as a binary release. Then, a new contributor can clone a project and run day10 build . without installing opam, configuring a switch, or even having OCaml on their system.
Extra arguments are passed through to dune build:
day10 build . @install
day10 build . @runtest
Test dependencies are included with --with-test:
day10 build --with-test . @runtest
For non-dune projects, a custom build command can be specified:
day10 build --command "make" .
Multiple opam repositories can be specified, which is useful for projects that depend on packages not yet in the main repository or that use a development overlay:
day10 build --opam-repository ~/custom-repo --opam-repository ~/opam-repository .
day10 build recursively discovers all .opam files in the project directory, including vendored subdirectories. These are pinned during dependency resolution, so the solver uses the local versions rather than pulling packages from the opam repository. The local packages themselves are not installed into the switch; dune builds them directly from the workspace source.
This means projects like ocluster, which vendors obuilder as a subdirectory with its own dune-project, work correctly. The vendored packages are built by dune as part of the workspace, while their transitive dependencies from the opam repository are provided as cached layers.
Common options can be set in ~/.day10 or a local .day10 file (which overrides the global one). The format is simple KEY=VALUE, and the keys are prefixed with DAY10_ before being passed to the environment:
CACHE_DIR=/var/cache/day10
OPAM_REPOSITORY=/home/user/opam-repository
Multiple opam repositories can be specified on separate lines:
OPAM_REPOSITORY=/home/user/opam-repository
OPAM_REPOSITORY=/home/user/custom-repo
Per-project settings like the compiler version or a custom build command can go in ./.day10:
OCAML_VERSION=ocaml.5.3.0
WITH_TEST=true
COMMAND=make
The layer cache is shared with day10 health-check, so dependencies are reused for local development and vice versa. The total cache size for all my projects is 5GB.
The container base images are currently built for the Debian OS family, which includes Debian and Ubuntu. The distribution and version are detected from the host system by default, or can be overridden with --os-distribution and --os-version. FreeBSD and Windows are supported by day10 health-check but not yet by day10 build.
The project code is available at mtelvers/day10.