Pangram verdict · v3.3
We believe that this document is primarily human-written, with some AI-generated and AI-assisted content detected
AI likelihood · overall
MixedArticle text · 1,659 words · 6 segments analyzed
Date: 2025-07-29
A camel sitting atop a dune in the middle of the desert. He wears his hard hat as he takes a break from all the building and running of OCaml code. Want to start building your own? Follow the tracks below.
Welcome to all Camleers We are back with another practical walkthrough for the newcomers of the OCaml ecosystem. We understand from the feedback we have gathered over the years that getting started with the OCaml Distribution can sometimes be perceived as challenging at first. That's why we keep it in mind when planning each post - to make your onboarding smoother and more approachable. Case in point: today's topic, which came to us during the making of our latest opam deep-dive: Opam 103: Bootstrapping a New OCaml Project with opam. It occured to us that we were assuming a level of familiarity with the toolchain that we had never explicitly explained or clarified. We decided to put together a short, practical guide for the newer developers, looking for quick, on-the-fly tutorials for OCaml. 🛠
A Camleer's basics: Dune If you're new to OCaml, or any other programming language for that matter, the first necessities you'll encounter are building, running, and testing your code. Fortunately, there is a powerful build system called dune that we can use. It is widespread and makes project setup and compilation straightforward. Understanding how dune works is a key step towards becoming productive in the OCaml ecosystem. In this article, we’ll walk you through the essentials of using dune to build libraries, executables, and tests, and to manage your project structure. Whether you're writing your first OCaml program or stepping into a new dune-based codebase, this guide will help you get up and running quickly.
We strongly believe that starting from scratch is key when approaching a brand new technical topic — and today's topic is no exception.
Anyone who has ever felt lost exploring a new codebase knows that minimal, toy examples are often the best way to build intuition.
Table of contents
A Camleer's basics: Dune
Ressources
Project metadata and build specification files
dune-project
dune file
Key stanzas
Build and run your project
dune build
dune build @doc
dune exec --
Test your project with Dune
Cram tests
dune runtest
Scaffolding with dune init
Ressources As said previously, this article was written in the context of the latest Opam 103: Bootstrapping a New OCaml Project with opam. That article explained how an OCaml developer should go about structuring an OCaml project when they intend to use it with opam. The point of today's topic is to focus on the other defining parameter of the structure of an OCaml project: your build system. The goal is to show how the workflows of opam and dune fit together, while giving you a solid introduction to the fundamentals of dune. We're using the same toy project helloer as basis for this rundown. It's a simple, well-scoped example with a structure that's idiomatic to both opam and dune, making it a great fit for illustrating the fundamentals without unnecessary complexity. Note that helloer was not created using dune init that we will introduce at the end of this article. First, it's important to understand how Dune works under the hood - so you know what it's generating for you, how to modify it confidently, and how it fits into your overall build workflow.
Consider checking Dune's official reference manual or visiting the official OCaml Discuss forum to reach out to the OCaml Community.
Project metadata and build specification files
dune-project Let's first start with the dune-project file since every Dune-driven project should have one at its root. This file is the entry point for your project and its contents are its metadata — which Dune uses to understand how your project is structured.
Said metadata includes things like:
the version of dune you're using;
important URLs for your projects lifecycles;
optional settings like dependencies licensing, documentation;
and even configuration for automatic opam file generation. More on that in Opam 103.
This information not only guides Dune, but also helps tools like opam understand how to build, distribute, and document your project. $ cat dune-project (lang dune 3.15) (package (name helloer))
(cram enable)
Note: The first line must be (lang dune X.Y) - with no comments or extra whitespace. This line determines which features and syntax dune will recognize.
NB: You will find all complementary information in the official docs 👈.
dune file A dune file is a build specification file that tells Dune how to compile the OCaml code within a specific directory. Usually there's one dune file per subdirectory, with the description of what's there - library, executable, or some tests.
Since our toy helloer project is flat in structure, we’ll place this file at the root of the project. $ cat dune (library (name helloer_lib) (modules helloer_lib) )
(executable (public_name helloer) (name helloer) (libraries cmdliner helloer_lib) (modules helloer) )
(test (name test) (libraries alcotest helloer_lib) (modules test) )
In effect, this tells dune:
how to build the OCaml files in that directory;
how libraries, executables, and test targets are defined.
Key stanzas In the context of Dune, a stanza is just a fancy word for a block of configuration. It tells the build system what kind of artifact you want to define — be it a library, an executable, a test, a documentation alias, or even an installable binary. Each stanza lives inside a dune file and follows a structured, declarative syntax. They’re usually grouped by purpose, and each type comes with its own expected fields. Each of these stanzas deserves a deeper dive, but here's a quick overview to get you started.
library stanza (library (name helloer_lib) (modules helloer_lib) )
A library stanza tells Dune how to compile a set of modules into a reusable package.
Purpose of this stanza:
defines a library named helloer_lib;
which will be built from the module helloer_lib.ml (by default, each .ml file defines a module with the same name);
and only the exposed modules should be listed here - that is, the modules that are meant to be part of the library's public API and usable by other parts of the project or by external code.
OCaml module names should match the filename, so helloer_lib.ml is expected to exist in this directory.
executable stanza (executable (public_name helloer) (name helloer) (libraries cmdliner helloer_lib) (modules helloer) )
An executable stanza explains how to bundle up some code into a runnable binary.
Purposes:
name: builds an executable named helloer;
needs libraries: external cmdliner (for CLI parsing) and internal helloer_lib (our own library);
public_name helloer: this makes the executable available publicly. It is used for dune install helloer in the opam file for instance.
You can learn about how to find and install cmdliner in opam in the latest Opam 103 blogpost, you'll find a simple breakdown of opam files there too .
test stanza (test (name test) (libraries alcotest helloer_lib) (modules test) )
What it does:
declares a test target named test, defined in the file test.ml. A test stanza registers the executable as part of the runtest rule alias, meaning it will be compiled and run automatically when you invoke dune runtest (or its alias dune test);
uses the alcotest testing library;
also uses helloer_lib to test its functionality.
Now your project is setup and structured. Next, let’s see how to build it.
Build and run your project
dune build As you can see below, the dune build @all command will build all targets defined in your dune files, it's the default behaviour of the dune build command. $ tree . ├── dune ├── dune-project ├── helloer_lib.ml ├── helloer.ml ├── helloer.opam └── test.ml $ dune build @all
$ tree -L 2 . ├── _build │ ├── default │ │ ├── helloer.exe // executable in its build dir │ │ ├── helloer_lib.cmxs // built library │ │ ├── test.exe // test executable │ │ └── [...] │ ├── install │ └── log ├── dune ├── dune-project ├──
helloer_lib.ml ├── helloer.ml ├── helloer.opam └── test.ml
Explanation:
@all is an alias that includes all buildable targets defined in your dune files: executables, libraries, tests, docs, etc;
it is useful for doing a full build to ensure everything compiles.
You can also use custom aliases (like @doc, @runtest, etc.), or define your own in your dune files.
dune build @doc Once your code builds and your project has a proper dune-project file, you can generate documentation using: $ dune build @doc
What it does:
uses odoc behind the scenes to build API docs from your OCaml code. This implies that installing odoc is mandatory to benefit from this feature, a simple opam install odoc will do just fine;
builds HTML files in _build/default/_doc/_html/.
Make sure your dune-project file includes a (package ...) stanza, and that your libraries are properly documented using OCaml comments (** your comment *). You can see generate the doc for the toy project here
NB: You will find all complementary information in the official docs 👈.
After building, you can view the generated docs: $ open _build/default/_doc/_html/index.html
This is great for checking your module interfaces or publishing documentation online.
dune exec -- This command is used to run executables defined in your project. So, something like: $ dune exec -- ./helloer.exe Hello OCamlers!! $ dune exec -- ./helloer.exe --gentle Welcome my dear OCamlers.
This tells dune to build the executable if necessary, then run it. The -- separates the dune options from the executable and its arguments. The first item after -- is the executable to run This can be:
A relative path to a built target, so: dune exec -- ./path/to/executable
A public name of an installed executable, meaning: dune exec -- ./helloer.
All additional arguments after the executable name (like --gentle) are passed to the executable itself. Essentially, dune exec -- COMMAND behaves the same way as calling dune install first and then COMMAND sequentially.