# Dockerfile for building an OPAM application, with dune/apt/opam caching

**URL:** https://discuss.ocaml.org/t/dockerfile-for-building-an-opam-application-with-dune-apt-opam-caching/16906
**Category:** Ecosystem
**Tags:** docker
**Created:** [July 3, 2025, 5:37pm UTC](https://discuss.ocaml.org/t/dockerfile-for-building-an-opam-application-with-dune-apt-opam-caching/16906 "2025-07-03T17:37:33Z")
**Posts on this page:** 2
**Page:** 1

<div class="post-metadata">

### Author: ![edwin](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/edwin/32/627_2.png) [@edwin](https://discuss.ocaml.org/u/edwin)
#### Post date: [July 3, 2025, 5:37pm UTC](https://discuss.ocaml.org/t/dockerfile-for-building-an-opam-application-with-dune-apt-opam-caching/16906/1 "2025-07-03T17:37:33Z")

</div>

I was writing some dockerfiles for building some OCaml applications recently, and realized that it is possible to write a Dockerfile that can build almost arbitrary Opam applications without even having to hardcode the package name.

I used `ocaml-dockerfile` to generate it (only the OS and OCaml version is hardcoded), so I thought I’d share it, it should work with both Podman and Docker:

```dockerfile
# syntax=docker/dockerfile:1

FROM ocaml/opam:debian-12-ocaml-4.14

# Update the opam repository (otherwise lockfile may contain a version we don't know about)
RUN git -C /home/opam/opam-repository pull origin master && opam-2.3 update

# Enable the dune cache in copy mode
# (hardlink mode would fail with EXDEV if cache is on separate disk/partition)
# This caches the build of opam dependencies using dune and the main application
ENV DUNE_CACHE="enabled" DUNE_CACHE_STORAGE_MODE="copy"

# `apt` in containers is usually configured to always clean up after operations
# When we have a cache mount for the APT package cache we don't want to clean up, since the cache would be ineffective.
# Also change OPAM to use the 0install solver, this is faster, especially when a lockfile is present.
RUN sudo rm -f /etc/apt/apt.conf.d/docker-clean && \
  opam-2.3 option solver=builtin-0install

# Use a workdir outside of $HOME, to avoid having .opam as a subdir of the build
WORKDIR /app
# Copy dependency definitions first. See https://docs.docker.com/build/cache/optimize/#order-your-layers
COPY ["*.opam", "*.opam.locked", "."]

# Install and cache system packages required by the build
# The cache is locked, so multiple container builds will wait here
# (apt would have a lock but it is stored outside of the cache dir, so we cannot rely on it to prevent concurrent accesses)
# A cache id is used, so that different distros would have different cache folders
RUN --mount=type=cache,id=/var/cache/apt#debian-12;amd64,target=/var/cache/apt,sharing=locked \
    --mount=type=cache,id=/var/lib/apt#debian-12;amd64,target=/var/lib/apt,sharing=locked \
    sudo apt-get update -y && \
    opam-2.3 install --locked --with-test . --depext-only

# Download and cache opam package dependencies
# The cache is locked, so multiple container builds will wait here
# To minimize the time the lock is held the actual package installations are done as a separate step
RUN --mount=type=cache,target=/home/opam/.opam/download-cache,sharing=locked,uid=1000,gid=1000 \
    opam-2.3 install --locked --with-test . --download-only

# Install (cached) downloaded opam dependencies
# The download cache is mounted readonly and shared, multiple container builds can proceed in parallel.
# The dune cache is mounted RW.
# Multiple concurrent builds will use the same cache, but dune must already be able to cope with this
# A cache id is used, so that different distros would have different cache folders (it is unlikely that dune caches would be sharable across distros)
RUN --mount=type=cache,target=/home/opam/.opam/download-cache,readonly,sharing=shared,uid=1000,gid=1000 \
    --mount=type=cache,target=/home/opam/.cache/dune,sharing=shared,uid=1000,gid=1000 \
    opam-2.3 install --locked --with-test . --deps-only

# Copy actual application source code
COPY [".", "."]

# Build and install application code, using dune cache.
RUN --mount=type=cache,target=/home/opam/.cache/dune,sharing=shared,uid=1000,gid=1000 \
    opam-2.3 install --locked --with-test .

```

It avoids some common pitfalls:

- disable cleanup of APT downloaded packages (otherwise caching is ineffective)
- enables the opam download cache too which is stored in a non-standard location (`~/.opam/download-cache` instead of `~/.cache/opam`)
- avoids EXDEV from dune cached builds during opam dependency installations by enabling dune cache copy mode
- uses opam-2.3 explicitly, since the default opam in the Dockerhub images is quite old (luckily opam-2.3 is already there, just needs to be invoked explicitly)
- sets the default solver to 0install. This seems to be needed to speed up `opam install` even when `--locked` is used and no dependency resolution is needed. Otherwise it was spending 4s checking the solver request.
- sets appropriate uid in cache mounts to avoid permission issues

Caveats:

- if you use git submodules then you have to add `.git` to your `.dockerignore`. Otherwise opam pinning will fail, since `.git` is a file referencing a git dir in a parent dir that is not mounted inside the docker build environment
- maybe --dev should be used if you intend to use the container for developing the opam application. Although in that case you might also want to preinstall some useful tools like `lsp`, and `ocamlformat`.

Eventually I hope this can be simplified using Dune’s new package management feature.

---

<div class="post-metadata">

### Author: ![jbeckford](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/jbeckford/32/3027_2.png) [@jbeckford](https://discuss.ocaml.org/u/jbeckford)
#### Post date: [July 3, 2025, 6:32pm UTC](https://discuss.ocaml.org/t/dockerfile-for-building-an-opam-application-with-dune-apt-opam-caching/16906/2 "2025-07-03T18:32:52Z")

</div>

That looks great!

Small nit:

> [@edwin](#):
>
> `--mount=type=cache,target=/home/opam/.cache/dune,`

Should that be:

```auto
--mount=type=cache,id=/home/opam/.cache/dune#debian-12:amd64,target=/home/opam/.cache/dune,

```

given the comment about “it is unlikely that dune caches would be sharable across distros”?
