What is your development environment for OCaml?

What OSs, editors, IDEs, plugins, build tools, testing tools, profiling tools, benchmark tools, deployment tools, linters, frameworks and most common libraries do you use for programming in OCaml? What really helps you do stuff with OCaml faster and better?


Personally, I use Emacs for OCaml development, and the main tools on top of this that I use for OCaml development are as follows:

  • Merlin for type checking/feedback etc.
  • Tuareg for syntax highlighting
  • ocp-indent for autoformatting code
  • Gopcaml-mode for structural editing.

Overall these combine to provide an incredibly fluid and ergonomic developement experience, completely unlike anything I’ve experienced in any other programming language:


I develop on FreeBSD, but often deploy to Linux. I use emacs + ocamllsp. I develop in a polyglot monorepo and use pds for my builds. I’d like to have pds output dune configuration eventually but I haven’t looked into it deeply yet. I also use mostly my own suite of libraries, based around a concurrency suite I have developed over the years. I’m off in the wild west…


I use neovim with ocamllsp + ocamlformat, on archlinux. I test with a
mix of dune tests, ounit2, alcotest, QCheck. I use containers, obviously :upside_down_face:.


I use vscode with the ocaml-lsp-server extension. It’s dreamy. I can run tools locally on my laptop, or on a server via ssh, or in a container, and the dev environment stays exactly the same.


Archlinux, neovim with fugitive (not ocaml specific but still coding related) and merlin.

For projects that use them I call formatting tools outside of the editor, not inside. (Or if inside, I do it via :! rather than some key binding.)

I tend to shop around rather than stick to specific libraries.

1 Like

A plug for ripgrep. It’s unbelievable fast. I couldn’t live without it. Combine it with sponge and you get a terrific global search-and-replace tool. I found this somewhere on the web:

# global replace string
set -o errexit -o pipefail
if [ "$#" -eq 0 ]
  echo "usage: rs PATTERN REPLACEMENT [PATH...]" > /dev/stderr
  echo "dry run: use rg"
  exit 1
shift 2
rg -l "$pattern" "${paths[@]}" | while IFS=$'\n' read -r file; do
  rg --passthru "$pattern" --replace "$replacement" "$file" | sponge "$file"

Umm … sorry for offtopic, but surely not all possble /bin/sh implementations have “errexit” and “pipefail”?

This conflation of generic shell and bash bothers me because it can lead to real confusing bugs. A script like this one that will just crash is the least of it.


1 Like

I typically edit source code in Sublime Text on a MacBook Pro M1 running macOS Monterey.

I usually have one or more Terminal windows open with zsh, and at home I’m usually using omake as the command line driver for running tests and building packages. For my day job, I’m usually using mosh over a VPN link to get a shell on my Linux development server, where I’m using bazel as the command line driver for running tests and building packages (but there is no OCaml at work).

To work on my OCaml projects, I’ve installed multiple compiler switches in opam, which I installed with brew. I tend to build and test on macOS first. I use Mercurial for my personal source code repositories, which I host on a network of FreeBSD servers (some in a cloud somewhere), and I use Bitbucket for hosting my public source code repositories. I use Jira (not a fan of it) to track my project issues and roadmap.

I use the Docker for Mac to build the containers (derived from the OPAM CI containers) that I use in Bitbucket pipelines to test my software on various Linux distributions before I make draft pull requests into the public OPAM repository.

1 Like

For development, tmux + podman + nvim (no plugin or lsp for the better or worse). I usually write a Dockerfile for each project these days.

For testing, alcotest, qcheck, and if needed crowbar+afl.

Containers is definitely a usual choice for me.

1 Like

For development, I use a simple Ubuntu (latest version without wayland). I develop with Emacs with the pokemacs configuration that automatically enable the perfect environment and tools for my needs in OCaml. It is also quite simple to configure, even for non emacs connoisseur like me.

For tests, I use dune tests, alcotest, QCheck.


That’s a really interesting package. I’ll take a look at it and I’m almost sure I’ll add it to my configuration :slight_smile:


VSCode and OCaml platform

  • OS: Linux (a mix of Fedora, Ubuntu and CentOS)
  • Editor: doom-nvim, you can see my config here. Used to be a homegrown Vim/Neovim config, and before that spacemacs.
  • “IDE”: ocaml-lsp is used automatically by doom-nvim above. ocamlformat for code formatting. I access this via SSH to a tmux instance, and works a lot faster than any graphical IDE would. Occasionally used VSCode+Neovim plugin+ocaml-platform. utop/dune utop to quickly try out small ideas.
  • Build: dune with caching enabled, also experimenting with git-branchless for managing all my changes and keeping various development code up-to-date with “upstream” changes, and managing my git tree as patches/PRs get merged upstream
  • Testing: a mix of alcotest, and monolith or qcstm
  • Profiling: flamegraphs with perf (using --call-graph=dwarf is a must to get a decent stacktrace), allocation hotspots with a mix of jemalloc’s builting heap profiler, and memtrace/memtrace_viewer
  • Debugging: mostly via additional logging statements, though I hope to introduce OpenTelemetry/OpenTracing into our codebase to make visualising the flow of API calls through components easier. gdb only when a C stub crashes, or to figure out where a multithreaded application is “stuck” (when its own builtin diagnostic fails).
  • Deployment: outside my control, it goes through koji currently. Bit of an inconvenience that you have to chain-build everything when you change a library deep down the stack, moving towards a monorepo helps avoid all those problems (and the builds are a lot faster than they ever were, Dune just parallelises builds a lot better, plus it avoids the overhead of packing up and unpacking intermediate RPMs, which can get quite large)