blog :: Post "intro-to-dune"
Introduction to Dune
2026-07-05 · 7 min
Dune builds OCaml (and Reason, and Coq) projects. This is most of the workflow, command by command.
A little history
Dune started life at Jane Street in 2016 as jbuilder, built to replace ocamlbuild and the hand-written Makefiles that most OCaml projects were carrying around. It was renamed to Dune in 2018 once it outgrew being "the Jane Street build tool" and became the default for the wider ecosystem. It composes with opam (which installs packages and manages switches) and ocamlfind/findlib (which used to be the thing resolving library names).
Install, init, build, run
Install it via opam.
$ opam install dune
Scaffold a new executable project, and step into it.
$ dune init project hello$ cd hello
What it generated.
hello/├── bin/│ ├── dune│ └── main.ml└── dune-project
Build it.
$ dune build
Run it.
$ dune exec hello
Rebuild on every save, instead of running dune build by hand.
$ dune build -w
Libraries, and wiring them together
Add a library alongside the executable.
$ dune init lib mylib lib
Wire the executable to depend on it, in bin/dune.
$ (executable$ (name main)$ (libraries mylib))
A library that will itself be installed needs a public_name, so other projects (and opam) can refer to it by a stable name rather than its in-tree path.
$ (library$ (name mylib)$ (public_name hello.mylib)$ (libraries str))
Multiple executables in one stanza, sharing dependencies.
$ (executables$ (names main admin)$ (libraries mylib))
Installing libraries with opam
Search for a package by name or keyword.
$ opam search yojson
Install a third-party library into the current switch.
$ opam install yojson lwt
Then depend on it from the library or executable stanza that needs it.
$ (executable$ (name main)$ (libraries mylib yojson lwt))
List everything opam has installed in the active switch.
$ opam list
Let Dune generate the project's .opam file(s) from dune-project, instead of hand-maintaining them. Add this once, then re-run dune build after editing dependencies.
$ (generate_opam_files true)$$ (package$ (name hello)$ (depends ocaml dune yojson lwt))
Flags worth knowing
Debug build (default profile).
$ dune build
Optimized release build - stricter warnings-as-errors, more inlining.
$ dune build --profile release
Type-check everything without producing binaries. Fast.
$ dune build @check
Run every test stanza in the tree.
$ dune test
A test's expected output changed on purpose? Accept the new output.
$ dune promote
Format every .ml/.mli file to the project's style.
$ dune fmt
Open a REPL (utop) with a library already loaded.
$ dune utop lib
Parallel jobs, verbosity, and scoping a command to one package - useful in CI and in larger monorepos.
$ dune build -j 8$ dune build --display=quiet$ dune build --only-packages hello
Remove every build artifact.
$ dune clean
Install the built package's binaries and libraries system-wide.
$ dune install
Flambda
Flambda is an alternative middle-end for the OCaml compiler that does much more aggressive cross-module inlining and specialization. It is not the default compiler variant - it lives behind an opam switch option, and it mostly pays off in --profile release builds where the extra compile time is worth it.
Create a switch with flambda enabled, then build with the release profile to actually benefit from it.
$ opam switch create 5.2.0+options ocaml-option-flambda$ dune build --profile release
Confirm flambda is actually on in the current switch.
$ ocamlfind ocamlopt -config | grep flambda
Or skip typing --profile release and -O3 every time: pin flags per profile in dune-project (or in any dune file, to scope it to one directory).
$ (env$ (release$ (ocamlopt_flags (:standard -O3))))
Different flags per profile, plus a catch-all for everything else.
$ (env$ (dev$ (flags (:standard -w -a)))$ (release$ (ocamlopt_flags (:standard -O3 -unboxed-types))))
That's most of the loop: build, exec, test, fmt, install libraries with opam, wire them into a stanza, and reach for --profile release plus flambda once performance actually matters.