Setting up an OCaml development environment is notoriously time-consuming:
- Compiling OCaml from source takes 30+ minutes
- Installing opam packages can fail due to system dependencies
- Configuring editors with LSP, merlin, and formatters requires expertise
- ThreadSanitizer (tsan) builds add further complexity
For tutorials and training sessions, this setup friction is a barrier:
- Attendees waste time on environment issues instead of learning
- Inconsistent environments lead to "works on my machine" problems
- Instructors spend time debugging setups instead of teaching
-
Zero-friction onboarding
- Codespaces: Click a link → working environment in <3 minutes
- Local:
devcontainer up→ ready to code - No OCaml/opam installation required on host
-
Multi-editor support
- Primary workflow:
devcontainer exec(works with any editor) - VS Code, Vim, Emacs, Claude Code all supported
- Same container, same tools, user's choice of editor
- Primary workflow:
-
Production-ready tooling
- Full debugging suite: gdb, valgrind, perf, rr
- OCaml profiling: landmarks, memtrace, olly
- Testing: ounit2, qcheck, ppx_expect, bisect_ppx
- Both standard (5.4.0) and tsan (5.4.0+tsan) switches
-
Fast iteration for tutorial authors
- Layered images: base (compilers) + dev (tools)
- Adding tutorial-specific packages: seconds, not minutes
- Test materials without rebuilding compilers
-
Reliability through testing
- CI tests all opam packages work (don't trust, verify)
- Multi-arch (amd64 + arm64) with native runners
- Editor integration tests for each workflow
- Supporting OCaml versions older than 5.4
- Windows native containers (WSL2 works fine)
- Minimal/stripped-down images (we optimize for completeness)
| Decision | Choice |
|---|---|
| Registry | Both Docker Hub + GHCR |
| Base image | Microsoft devcontainers/base (see comparison below) |
| Editor support | Primary: devcontainer exec from host. Vim/Emacs also in base image. |
| Dev tools | Full suite, identical in BOTH switches |
| Package management | Both: traditional opam AND dune pkg workflows |
| Debug/Profile | Full suite: gdb, rr, valgrind, perf, memtrace, landmarks |
| Testing scope | Full integration (LSP protocol, editor workflows) |
| Image strategy | Separate images (base + dev) for fast tutorial iteration |
| GitHub Codespaces | Explicit support (config, docs, CI testing) |
| Shell config | Both: postStartCommand + .bashrc (covers all scenarios) |
| Claude Code | Include via DevContainer Feature (only editor inside container) |
| Architectures | Multi-arch (amd64 + arm64), QEMU fallback if no native ARM runners |
| Hosting | New GitHub repository (organization) |
| MCP support | odoc-llm (remote) documented |
Create a DevContainer setup for OCaml 5.4 and 5.4+tsan that works with VS Code, Emacs, Neovim, and Claude Code. Includes automated CI/CD for Docker builds and multi-editor testing.
ocaml-devcontainer/
├── base/
│ └── Dockerfile # ocaml-devcontainer-base image
├── dev/
│ └── Dockerfile # ocaml-devcontainer image (FROM base)
├── .devcontainer/
│ └── devcontainer.json # Uses pre-built ocaml-devcontainer
├── .devcontainer-from-scratch/
│ └── devcontainer.json # Builds dev locally (for customization)
├── .github/
│ └── workflows/
│ ├── build-push.yml # Build & push to registries
│ └── test.yml # Full integration tests
├── test/
│ ├── test-ocaml.sh # Verify OCaml compilers + tools (both switches)
│ ├── test-lsp.sh # Full LSP protocol tests (both switches)
│ ├── test-profiling.sh # landmarks, memtrace, olly, bisect_ppx
│ ├── test-dune-pkg.sh # Dune package management workflow
│ ├── test-vscode.sh # VS Code devcontainer integration
│ ├── test-neovim.sh # Neovim connection pathways
│ ├── test-emacs.sh # Emacs TRAMP + eglot integration
│ ├── test-claude.sh # Claude Code installation + execution
│ └── lsp-client.py # Helper: minimal LSP client for testing
├── examples/
│ ├── hello/ # Simple dune project (opam workflow)
│ │ ├── dune-project
│ │ ├── dune
│ │ └── hello.ml
│ ├── with-tests/ # Project with inline/expect tests (opam)
│ │ ├── dune-project
│ │ ├── dune
│ │ ├── lib.ml
│ │ └── lib_test.ml
│ └── dune-pkg-demo/ # Dune package management demo
│ ├── dune-project # With (depends ...)
│ ├── dune-workspace # With (pkg enabled)
│ ├── dune
│ └── main.ml
├── docs/
│ ├── SETUP-CODESPACES.md # GitHub Codespaces (zero-install, tutorial attendees)
│ ├── SETUP-DEVCONTAINER-EXEC.md # Primary workflow: devcontainer exec
│ ├── SETUP-VSCODE.md # VS Code "Reopen in Container"
│ └── SETUP-ADVANCED.md # TRAMP, nvim plugins, custom setups
├── DEVCONTAINER.md # Quick start + overview
└── README.md # Project overview
Base image: mcr.microsoft.com/devcontainers/base:ubuntu-24.04
OCaml switches to create:
| Switch | Description |
|---|---|
5.4.0 |
Standard OCaml 5.4 compiler (default) |
5.4.0+tsan |
OCaml 5.4 with ThreadSanitizer for race detection |
Editors in base image (via apt):
- vim
- emacs
Editor config files (quality-of-life): To ensure "inside the container" editing matches "devcontainer exec" experience:
.emacs- minimal config enabling eglot + ocaml-lsp-serverinit.lua(Neovim) - minimal config callingvim.lsp.startfor OCaml
This prevents users who SSH in from getting a "naked" editor without LSP.
OCaml development tools (installed identically in BOTH switches):
Build & Editor Support:
- dune (build system)
- ocaml-lsp-server (LSP for all editors)
- merlin (code intelligence backend)
- ocamlformat (code formatter)
- utop (interactive REPL)
- odoc (documentation generator)
Testing:
- ounit2 (unit testing)
- ppx_inline_test (inline tests)
- ppx_expect (expect tests)
- qcheck (property-based testing)
- bisect_ppx (code coverage)
Libraries:
- core (Jane Street standard library)
- base (minimal Jane Street stdlib)
Profiling & Debugging:
- landmarks, landmarks-ppx (instrumentation profiling)
- memtrace (memory profiling via statmemprof)
- runtime_events_tools (olly - runtime events / GC latency)
- printbox (pretty-print data structures)
Dockerfile pattern for identical switches:
# Define tools once, install in both switches
ENV OCAML_BUILD="dune ocaml-lsp-server merlin ocamlformat utop odoc"
ENV OCAML_TEST="ounit2 ppx_inline_test ppx_expect qcheck bisect_ppx"
ENV OCAML_LIBS="core base"
ENV OCAML_PROFILE="landmarks landmarks-ppx memtrace runtime_events_tools printbox"
ENV OCAML_TOOLS="$OCAML_BUILD $OCAML_TEST $OCAML_LIBS $OCAML_PROFILE"
RUN opam install --switch=5.4.0 -y $OCAML_TOOLS && \
opam install --switch=5.4.0+tsan -y $OCAML_TOOLS && \
opam clean -aSystem dependencies:
- m4, build-essential, autoconf, pkg-config
- libunwind-dev (stack traces)
- git, curl, ssh
- sudo (vscode user needs it)
Debugging & profiling tools (apt) - in base image:
- gdb, lldb (native debuggers)
- valgrind (memcheck, massif, helgrind, drd)
- strace, ltrace (syscall/library tracing)
- linux-tools-generic, perf (CPU profiling)
- rr (record & replay, reverse debugging) *
- bpftrace (eBPF tracing)
- hyperfine (CLI benchmarking)
- inferno (flamegraph generator, via cargo)
*Note: rr requires hardware perf counters. Works on bare metal and some VMs (not most cloud VMs). Document fallback to valgrind if unavailable.
{
"name": "OCaml 5.4 Development",
"image": "ghcr.io/tarides/ocaml-devcontainer:latest",
"features": {
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}
},
"customizations": {
"vscode": {
"extensions": [
"ocamllabs.ocaml-platform"
]
}
},
"postStartCommand": "eval $(opam env)",
"remoteUser": "vscode",
// Persist dune pkg cache across container rebuilds
"mounts": [
"source=dune-cache,target=/home/vscode/.cache/dune,type=volume"
],
"remoteEnv": {
"DUNE_CACHE_ROOT": "/home/vscode/.cache/dune"
},
// GitHub Codespaces configuration
"hostRequirements": {
"cpus": 4,
"memory": "8gb",
"storage": "32gb"
},
"codespaces": {
"openFiles": ["README.md"]
}
}Why explicit support matters for tutorials:
- Attendees need zero local setup (no Docker installation)
- Click "Open in Codespaces" → working environment in ~2 minutes
- Consistent environment across all participants
Configuration includes:
hostRequirements: Ensure adequate resources for OCaml compilationcodespaces.openFiles: Welcome file opens automatically- Pre-built image: Fast startup (no build wait)
Repository setup:
- Add "Open in Codespaces" badge to README
- Prebuild configuration (optional):
.github/codespaces/prebuild.yml
Documentation:
docs/SETUP-CODESPACES.md: Step-by-step for attendees- Troubleshooting common issues (storage, timeouts)
- Trigger: Push to main, tags, manual dispatch
Job: build-base
- Condition: Only if
base/changed OR manual dispatch OR tag - Steps:
- Set up QEMU (for ARM emulation if no native runners)
- Set up Docker Buildx
- Login to Docker Hub + GHCR
- Build multi-arch
ocaml-devcontainer-base(amd64, arm64) - Push to both registries
- Caching: Use GitHub Actions cache (
type=gha) - Note: Test ARM runner availability early; fall back to QEMU if needed
Job: build-dev
- Needs: build-base (waits if base is building)
- Condition: Always runs on push to main
- Steps:
- Pull latest
ocaml-devcontainer-base - Build multi-arch
ocaml-devcontainer - Push to both registries
- Pull latest
-
Trigger: PR, push to main, after build-push completes
-
Jobs:
1. test-ocaml (matrix: switch × arch)
matrix: switch: [5.4.0, 5.4.0+tsan] arch: [amd64, arm64] runs-on: amd64: ubuntu-latest arm64: ubuntu-24.04-arm # If available (paid/Enterprise) # Fallback: QEMU emulation via docker/setup-qemu-action
ARM Runner Note: Native ARM runners (
ubuntu-24.04-arm) require GitHub paid/Enterprise tier. Free tier must use QEMU emulation viadocker/setup-qemu-action, which is ~10x slower but functional. Test early to determine which path to use.- Compiler version check
- Compile and run sample program
- Dune build + test
- Verify all tools installed
2. test-lsp (matrix: switch)
matrix: switch: [5.4.0, 5.4.0+tsan]
- Full LSP protocol test (initialize, hover, completion, formatting)
3. test-profiling (matrix: switch)
matrix: switch: [5.4.0, 5.4.0+tsan]
- landmarks, memtrace, olly, bisect_ppx (opam packages)
4. test-dune-pkg
- dune pkg lock, dune build with (pkg enabled)
- dune tools exec ocamllsp/ocamlformat
- Verify both package management workflows work
5. test-editors (matrix: editor)
matrix: editor: [vscode, neovim, emacs, claude]
- Editor-specific integration tests
Testing philosophy:
- Trust apt (distro packages): gdb, valgrind, perf, vim, emacs - just install, don't test
- Test opam packages: dune, LSP, merlin, landmarks, memtrace - verify they work
Scope: Full integration testing for opam-installed tools and editor workflows.
- Verify switch exists:
opam switch list - Check compiler version:
ocaml -version - Compile sample program:
ocamlopt -o hello hello.ml && ./hello - Build with dune:
dune build - Run tests with dune:
dune test - Verify all tools present: ocamlformat, utop, merlin
- Start ocaml-lsp-server
- Send LSP
initializerequest - Send
textDocument/didOpenwith sample file - Request
textDocument/hover- verify response - Request
textDocument/completion- verify suggestions - Request
textDocument/formatting- verify ocamlformat integration - Shutdown cleanly
- Use
devcontainerCLI to start container - Verify VS Code extensions would load (check extension manifest)
- Simulate LSP connection via stdio
- Start container with known name
- Use
docker execto verify TRAMP path would resolve - Test LSP via JSON-RPC over
docker exec - Verify eglot-compatible responses
- Test
devcontainer execpathway works - Verify LSP responds correctly via stdio
- Test path mapping (host path ↔ container path)
- Verify Claude Code is installed:
claude --version - Test basic command execution
- Verify file access within workspace
- landmarks: Instrument sample code, verify report generated
- memtrace: Generate trace file, verify CTF output
- olly: Capture runtime events from sample program
- bisect_ppx: Run coverage on sample, verify report
- Create test project with
(pkg enabled)in dune-workspace - Add dependencies in dune-project
- Run
dune pkg lock- verify lock directory created - Run
dune build- verify deps downloaded and built - Run
dune tools exec ocamllsp -- --version- verify works - Run
dune tools exec ocamlformat -- --version- verify works - Test
eval $(dune tools env)sets PATH correctly
DEVCONTAINER.md will cover:
- Quick start (5 min) with pre-built image
- Local build option (30+ min)
- Editor-specific setup:
- VS Code: Just open in container
- Neovim:
devcontainer exec --workspace-folder . nvim - Emacs: TRAMP method
/docker:<container>:/path - Claude Code: Pre-installed via feature
- Switching between OCaml versions
- Troubleshooting common issues
- Initialize repository structure
- Write
base/Dockerfile:- Base:
mcr.microsoft.com/devcontainers/base:ubuntu-24.04 - Install opam and system dependencies (include
sudo!) - Install vim, emacs (apt)
- Create both switches (5.4.0, 5.4.0+tsan)
- Use ENV pattern to avoid code duplication between switches
- Configure shell:
eval $(opam env)in .bashrc - Add minimal editor configs (.emacs, init.lua) for LSP
- NO OCaml tools yet (just compilers + editors)
- Base:
- Write
build-push.ymlimmediately to test ARM runner access- If native ARM runners unavailable, configure QEMU fallback
- Write
dev/Dockerfile:- FROM ocaml-devcontainer-base
- Install identical OCaml toolset in both switches
- Clean opam cache
- Create
.devcontainer/devcontainer.json:- Uses pre-built ocaml-devcontainer image
- Adds Claude Code via DevContainer Feature
- Configures Codespaces requirements
- Create
.devcontainer-from-scratch/devcontainer.jsonfor local builds
- Create build-push.yml workflow:
- Job 1: Build base (only if base/ changed or manual)
- Job 2: Build dev (depends on base, always runs)
- Multi-arch builds (amd64 + arm64)
- Push to Docker Hub + GHCR
- Set up repository secrets documentation
- Create example OCaml projects:
examples/hello/- minimal dune projectexamples/with-tests/- project with ppx_inline_test, ppx_expect
- Write
test/lsp-client.py- minimal LSP test client - Write test scripts:
test-ocaml.sh- compiler + tools verification (both switches)test-lsp.sh- full LSP protocol testingtest-vscode.sh- devcontainer CLI integrationtest-neovim.sh- exec pathway + LSP connectiontest-emacs.sh- TRAMP simulation + LSPtest-claude.sh- Claude Code verification
- Create test.yml workflow with matrix strategy
- Write setup guides (emphasize
devcontainer execas primary):docs/SETUP-CODESPACES.md- zero-install for tutorial attendeesdocs/SETUP-DEVCONTAINER-EXEC.md- primary workflow (all editors)docs/SETUP-VSCODE.md- VS Code "Reopen in Container"docs/SETUP-ADVANCED.md- TRAMP, nvim plugins, etc.
- Write
DEVCONTAINER.md- quick start overview - Write
README.md:- Project overview
- "Open in Codespaces" badge
- Primary:
devcontainer execexamples - Links to detailed guides
After implementation, verify:
docker build -t ocaml-devcontainer-base base/succeedsdocker build -t ocaml-devcontainer dev/succeeds (uses local base)devcontainer up --workspace-folder .starts container- Both switches work:
opam switch 5.4.0 && ocaml -version - All tools present in both switches (identical)
- LSP responds: run
test/test-lsp.sh
- GitHub Actions builds both images (base, dev)
- Multi-arch manifests correct (amd64 + arm64)
- Images pushed to both Docker Hub and GHCR
- All matrix tests pass (switches × editors)
- Codespaces: Create codespace from repo, verify startup < 3 min
- VS Code: "Reopen in Container" works, LSP provides completions
- Neovim:
devcontainer exec nvim examples/hello/hello.ml- LSP works - Emacs: TRAMP + eglot connects to LSP
- Claude Code:
claudecommand works inside container
- Switch to tsan:
opam switch 5.4.0+tsan - Verify switch works: compile and run sample program (trust tsan if switch builds)
- landmarks: Profile sample OCaml code, view report
- memtrace: Generate trace, verify CTF output
- olly: Capture runtime events
- bisect_ppx: Generate coverage report
dune pkg lockcreates lock directorydune buildwith(pkg enabled)worksdune tools exec ocamllspruns correctlydune tools envsets up PATH
- Create minimal tutorial Dockerfile (FROM ocaml-devcontainer)
- Add one opam package → verify fast rebuild (<2 min)
- Test dune pkg workflow in tutorial context
Before running CI/CD, you'll need to set up:
-
GitHub repository secrets:
DOCKERHUB_USERNAME- Your Docker Hub usernameDOCKERHUB_TOKEN- Docker Hub access token (not password)
-
Replace placeholders in files:
tarides- Your GitHub organization name<DOCKER_USERNAME>- Your Docker Hub username<REPO_NAME>- Repository name (e.g.,ocaml-devcontainer)
The images will be published to:
docker.io/<DOCKER_USERNAME>/<REPO_NAME>:latestghcr.io/tarides/<REPO_NAME>:latest
- Architectures: linux/amd64, linux/arm64
Use case: Tutorials and training sessions need stable base + fast iteration.
┌─────────────────────────────────────────────────────────┐
│ ocaml-devcontainer-base │
│ ───────────────── │
│ • Ubuntu 24.04 + system deps │
│ • opam initialized │
│ • Switch: 5.4.0 │
│ • Switch: 5.4.0+tsan │
│ • vim, emacs (apt) │
│ • gdb, lldb, valgrind, rr, perf, strace (apt) │
│ • bpftrace, hyperfine, inferno (apt/cargo) │
│ • Shell config: eval $(opam env) in .bashrc │
│ │
│ Build time: ~35-50 min (multi-arch) │
│ Rebuild frequency: Rare (new OCaml release) │
│ Size: ~2.2 GB │
└───────────────────────────┬─────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ ocaml-devcontainer │
│ ───────────────── │
│ FROM ocaml-devcontainer-base │
│ • Build: dune, ocaml-lsp-server, merlin, ocamlformat │
│ • REPL/Docs: utop, odoc │
│ • Testing: ounit2, ppx_inline_test, ppx_expect, qcheck │
│ • Coverage: bisect_ppx │
│ • Profiling: landmarks, memtrace, olly │
│ • Libraries: core, base │
│ (identical in both switches) │
│ • Claude Code (via DevContainer Feature) │
│ │
│ Build time: ~15-20 min │
│ Rebuild frequency: When tools update │
│ Size: ~3.5 GB │
└───────────────────────────┬─────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ [tutorial-specific] (optional, user-created) │
│ ───────────────────────────────────── │
│ FROM ocaml-devcontainer │
│ • Tutorial-specific opam packages │
│ • Exercise files, starter code │
│ │
│ Build time: seconds to minutes │
│ Rebuild frequency: During tutorial development │
└─────────────────────────────────────────────────────────┘
| Image | Tag | Description |
|---|---|---|
ocaml-devcontainer-base |
latest, 5.4.0 |
Compilers only |
ocaml-devcontainer |
latest, 5.4.0 |
Full development environment |
Both published to:
docker.io/<DOCKER_USERNAME>/ocaml-devcontainer-basedocker.io/<DOCKER_USERNAME>/ocaml-devcontainerghcr.io/tarides/ocaml-devcontainer-baseghcr.io/tarides/ocaml-devcontainer
# build-push.yml
jobs:
build-base:
# Only runs if base/ changed or manual trigger
# Builds and pushes ocaml-devcontainer-base
build-dev:
needs: build-base # Wait for base if it's building
# Always runs
# Pulls latest base, builds and pushes ocaml-devcontainer# Example: my-eio-tutorial/Dockerfile
FROM ghcr.io/yourorg/ocaml-devcontainer:latest
# Add tutorial-specific packages (fast, base already has compilers)
RUN opam install -y eio eio_main
# Add exercise files
COPY exercises/ /workspace/exercises/Iteration cycle: edit exercises → rebuild → test → repeat (seconds, not minutes)
Decision: Use mcr.microsoft.com/devcontainers/base:ubuntu-24.04
| Aspect | ocaml/opam |
mcr.microsoft.com/devcontainers/base |
|---|---|---|
| Default user | opam (uid 1000) |
vscode (uid 1000) |
| opam pre-installed | Yes, fully initialized | No, must install manually |
| Image size | Smaller (one compiler per image) | Larger base, but more flexible |
| opam-repository | Pre-checked out at /home/opam/opam-repository |
Must clone/init |
| DevContainer features | Not designed for them | Full support |
| Editors/tools | Minimal | Common dev tools included |
| Sandboxing | Bubblewrap available (disabled) | None |
With ocaml/opam:
- Each image tag = one compiler version (e.g.,
ocaml/opam:debian-ocaml-5.4) - Multiple switches require manual creation or multiple images
- Environment pre-configured via
/Dockerfile.ocaml - Best practice: use
opam exec --rather thaneval $(opam env)
With Microsoft base:
- Start from scratch:
opam init, thenopam switch create - More Dockerfile commands, but explicit control
- Must handle environment setup carefully
- Need multiple switches in one container (5.4.0, 5.4.0+tsan)
- Better DevContainer features integration (Claude Code feature)
- Explicit control over opam initialization
- Follows OxCaml tutorial pattern (proven approach)
To support ocaml/opam as an alternative later:
# Alternative Dockerfile.ocaml-opam
FROM ocaml/opam:debian-ocaml-5.4
RUN opam switch create 5.4.0+tsan ocaml-variants.5.4.0+options ocaml-option-tsan
RUN opam exec -- opam install -y dune ocaml-lsp-server merlin ocamlformat utop
# User is already 'opam', adjust devcontainer.json remoteUser accordinglyCould provide both via:
.devcontainer/devcontainer.json(Microsoft base, default).devcontainer-ocaml-opam/devcontainer.json(ocaml/opam base, alternative)
Primary workflow: Use devcontainer exec from host to run tools inside container.
Convenience: vim, emacs, Claude Code are also available inside the container.
| Editor | Primary (recommended) | Alternative |
|---|---|---|
| VS Code | "Reopen in Container" | - |
| Vim | devcontainer exec vim file.ml |
Also installed in base image |
| Neovim | devcontainer exec nvim file.ml |
Install in container if needed |
| Emacs | devcontainer exec emacs file.ml |
Also installed in base image |
| Claude Code | devcontainer exec claude |
Pre-installed in dev image |
Base image (ocaml-devcontainer-base):
- OCaml compilers (5.4.0, 5.4.0+tsan)
- vim, emacs (via apt)
- opam initialized, shell configured
Dev image (ocaml-devcontainer):
- Everything in base, plus:
- LSP server (ocaml-lsp-server)
- Build tools (dune)
- Development tools (merlin, ocamlformat, utop)
- Claude Code (via DevContainer Feature)
Emphasize devcontainer exec as primary:
# Start container
devcontainer up --workspace-folder .
# Run any editor/tool
devcontainer exec --workspace-folder . vim src/main.ml
devcontainer exec --workspace-folder . emacs src/main.ml
devcontainer exec --workspace-folder . claude
devcontainer exec --workspace-folder . dune buildMention alternatives:
- VS Code: Native "Reopen in Container" experience
- Emacs TRAMP:
/docker:container:/pathfor remote editing - Neovim plugins:
nvim-dev-containerfor VS Code-like experience
- Consistent: Same workflow for all editors
- Simple: No complex host-container LSP bridging
- Flexible: Users can still use native integrations if preferred
- Tutorial-friendly: One command pattern to teach
The container supports both package management workflows:
For existing projects and tutorials using opam:
# Tools pre-installed in both switches
opam switch 5.4.0
dune build
ocamllsp # Pre-installed, works immediately
ocamlformat # Pre-installedFor projects using Dune's integrated package management:
;; dune-project
(lang dune 3.17)
(name my-project)
(package
(name my-project)
(depends
(ocaml (>= 5.4))
(lwt (>= 5.7))))
;; dune-workspace
(lang dune 3.21)
(pkg enabled)dune pkg lock # Create lock directory
dune build # Downloads + builds all deps
dune tools exec ocamllsp # Project-local LSP
eval $(dune tools env) # Set PATH for editors| Component | Purpose |
|---|---|
| opam | Repository metadata for dune solver, traditional workflow |
| opam switches | Pre-configured 5.4.0 and 5.4.0+tsan with tools |
| dune 3.21+ | Latest dune with pkg support |
| Pre-installed tools | ocamllsp, ocamlformat, merlin (global, for opam workflow) |
| dune tools support | Works out-of-box for per-project tools |
- Existing tutorials use opam workflow
- New projects benefit from dune pkg (reproducible, per-project deps)
- Transition period - ecosystem moving toward dune pkg
- Tutorial authors can choose which approach to teach
opam workflow:
# Tools in PATH, editors find them automatically
devcontainer exec ocamllspdune pkg workflow:
# Option A: Run via dune tools
devcontainer exec dune tools exec ocamllsp
# Option B: Set up environment first
eval $(dune tools env)
ocamllsp # Now in PATHThe container tests verify:
- opam switches work with pre-installed tools
dune pkg lockresolves dependencies correctlydune buildwith(pkg enabled)builds projectsdune tools exec ocamllspworks- Both workflows produce working LSP integration