This is a cross-post in Markdown format that you can find here: https://discuss.ocaml.org/t/ann-uniker-nl-unikernels-in-ocaml/18625 We are delighted to announce the launch of our new website: [uniker.nl][uniker.nl]. This website embodies our new approach to developing unikernels with OCaml 5. It offers a new way of developing unikernels and now places just as much emphasis on the deployment and use of unikernels. The website features a comprehensive [tutorial][uniker.nl-tutorial] and resources to help you get to grips with our new ecosystem, providing a _first entry point_ for anyone interested in unikernels in OCaml. The content of this site is available [here][uniker.nl-repo], is powered by the excellent [YOCaml][yocaml] project, and is deployed using our [immuable][immuable] unikernel. ## Our new ecosystem We were able to present our new workflow at the [OCaml Workshop 2026][workshop] ([slides][slides]) and our approach can be summarised in two main points: - the development of unikernels - the deployment of unikernels and their uses ### To develop unikernels Our co-operative has been developing unikernels for quite a few years now. [Our blog][blog-robur] showcases some of the ones we use for our infrastructure. Based on our experience, we are well aware of the constraints and limitations of what we can achieve when it comes to developing an application in the form of a unikernel. The best-known constraint we have accepted is not to require the POSIX interface (nor to attempt to replicate it) in terms of protocols and formats. One of the rules still adhered to today is, amongst other things, [not to depend on I/O or a scheduler][sans-io][1][sans-io-2] when providing implementations. Building on this experience, we have been able to develop protocols and formats that are currently being used by others in contexts other than unikernels. However, we have also seen the limits of what we can achieve, particularly with what MirageOS currently offers. With this in mind, and upon further reflection on the constraints we face when developing unikernels (particularly regarding cross-compilation), we have decided to structure the development of unikernels using two tools: [`unic`][unic] and [`mfetch`][mfetch]. #### [`unic`][unic]: how to infer dependencies and _vendor_ them One constraint we face with unikernels is the need to recompile C files with specific options so that they do not depend on what already exists on the host system. The approach taken by MirageOS 3 was to distribute the result of this specific compilation within libraries that contain C stubs (such as [`digestif`][digestif] or [`mirage-crypto`][mirage-crypto]). The problem is that we were forcing users of these libraries to install [`ocaml-solo5`][ocaml-solo5] in order to obtain a compilation environment for C files specific to [Solo5][solo5], our core component for producing unikernels. MirageOS 4 then arrived with `dune` and its contexts, allowing us to achieve a consistent compilation environment for C files. However, this approach introduced new a constraint: all dependencies of our unikernels had to use `dune`, which is **legitimately** not the case in the OCaml ecosystem. It is for this reason that we decided to refine the need to recompile some of our libraries a little further and, finally, to be able to capitalise on a key advantage of [`ocaml-solo5`][ocaml-solo5], namely that compiling OCaml files with this compiler produces exactly the same result as if you were using your host OCaml compiler: in other words, and as has been the case from the beginning, we strictly only need to cross-compile the C files. It is therefore at the intersection of these two features (only needing to cross-compile the C files and being able to reuse libraries written in pure OCaml and compiled with the host compiler) that we can offer users the ability to write this sort of code without involving any maintenance work on libraries that do not use `dune` and without necessarily having to recompile everything: ```shell $ opam install unic $ cat >main.ml< open Digestif > let () = Mkernel.run [] @@ fun () -> > Fmt.pr "%a\n%!" SHA1.pp (SHA1.digest_string "Hello World!") > EOF $ unic infer -r . Module Digestif is provided by several ocamlfind packages: [0] digestif.c [1] digestif.ocaml Pick one [0-1]: 0 digestif mkernel $ opam source digestif $ opam source mkernel $ dune build $ solo5-hvt -- _build/solo5/main.exe --solo5:quiet 2ef7bde608ce5404e97d5f042f95f89f1c232871Solo5 ``` `unic` is therefore a tool capable of inferring the dependencies of an OCaml project without using a specific build system, and of calculating what needs to be cross-compiled (in this case `digestif` and `mkernel` but not `fmt`) to produce a unikernel. The project serves our purpose first and foremost, but it is worth noting that it can also be useful when compiling statically linked executables. This tool offers a whole range of sub-tools for analysing META files, viewing an artefact from different perspectives (its module name, its `ocamlfind` package name and its OPAM package name) or analysing OCaml artefacts (such as `*.cmxa` files). We would particularly like to thank @Octachron for his [`codept`][codept] project (on which `unic` is based) and for working with us to improve it. ### [`mfetch`][mfetch]: how to vendor dependencies Having identified more precisely which dependencies need to be cross-compiled, we now need to download these dependencies. Although `opam source` does the job very well, we wanted a tool that could download the source code from various types of source, such as: - from a _tarball_, as is usually the case - but also from a Git repository - or even from a specific location on your file system That is why we developed `mfetch`, which allows you to download software sources based on information provided by OPAM or the user, and to create a `vendors` folder containing all these sources. ```shell $ unic infer -r . -o _mfetch $ mfetch --with-dune-file digestif │██████████████████│ 3.1 MiB / 3.1 MiB 5.9 MiB/s mkernel │██████████████████│ 37.3 KiB / 37.3 KiB 3.4 MiB/s digestif ok mkernel ok $ dune build ``` `mfetch` also allows you to check whether you have modified any of the source files (for a bug fix) or to generate a `*.locked` file so that you can then verify the reproducibility of your unikernel using our other tool [`orb`][orb]. The tool itself is not revolutionary, but it helps to streamline the workflow. ```shell $ mfetch status digestif ok mkernel ok $ mfetch lock 2 sources locked into _mfetch.locked ``` #### Libraries for OCaml 5 Our new workflow now uses OCaml 5. As well as updating the basic building blocks required to produce unikernels with OCaml 5, we have also developed a series of libraries that primarily enable us to take advantage of effects. Furthermore, over the past several years we have developed: - [Miou][miou-announce], our scheduler for OCaml 5 - [`mkernel`][mkernel], a derivative of our Miou scheduler designed to handle the I/O provided by Solo5 - [`mnet`][mnet-announce], a reimplementation of the TCP/IP stack (using [`utcp`][utcp]) in OCaml with effects - and plenty of other libraries such as [`mhttp`][mhttp], [`msendmail`][msendmail], [`mfat`][mfat], [`vifu`][vifu], etc. These libraries, currently used by some of our deployed unikernels, allow us to offer developers a _direct-style_ approach to implementing their unikernels. But what is key to take away from this emerging ecosystem is the retrospective analysis of past projects to ensure we do not repeat the same mistakes. We very quickly realised that it could be very difficult to migrate existing libraries using [`lwt`][lwt] (the MirageOS scheduler) to Miou (or any other scheduler using effects). We therefore took the opportunity not only to propose a _direct-style_ approach using Miou, but also to rethink the implementation of our libraries in light of what we've learnt from deploying unikernels with MirageOS. We have paid particular attention to memory usage, to avoiding known CVEs from our previous implementations (as we are also part of the OCaml security team), and also to offering an alternative API that does not systematically rely on functors. The approach is still experimental, as we need to consolidate this new ecosystem across several layers, but we are firmly convinced (based on what we've observed in our deployed unikernels) that these new implementations are significantly better than what we used in the past. ### To deploy unikernels The other side of unikernels is deployment. Indeed, it is not just a matter of developing unikernels (and making their development easier) but also of deploying and using them. Our main objective remains to replace existing services with unikernels (name servers, websites, time servers, etc.), and the way to compete with existing infrastructure is to actively deploy and use our unikernels. That is why we have developed two projects: - [Albatross][albatross], an OCaml service for launching unikernels - [Aussi][aussi], an OCI runtime for Solo5 We mainly use Albatross in conjunction with our infrastructure for verifying the reproducibility of our unikernels: [builds.robur.coop][builds.robur.coop ]. In this way, we can trace the dependencies of the unikernels - particularly their CVEs - and ensure supply-chain security. Next comes deployment, and we are therefore seeking to integrate our unikernels into existing infrastructure and provide ways of monitoring them, in particular using [InfluxDB][influxdb], [Telegraf][telegraf] and [Grafana][grafana]. This is how we are able to offer services such as (note that some haven't yet been ported to `mkernel`/`mnet`, but will be soon): - simple websites such as our https://blog.robur.coop/ (using [`unipi`][unipi] or [`immuable`][immuable]) - domain name management for domains such as `robur.coop`, `reyn.ir` or `din.osau.re` (via our [primary][dns-primary-git] DNS server and [secondary][mnet-secondary] server) - a VPN service, whether using [MirageVPN][MirageVPN] or [Zostera][zostera] - a time server (using [Chaos][chaos]): https://chaos.osau.re/ - a [CalDAV][caldav] calendar system - [a mailing list][mailingl.st] using the [`ptt`][ptt] project - a TLS termination proxy such as [`tlstunnel`][tlstunnel] or [`contruno`][contruno] (used by sherlocode.com) - and many more! It is therefore through actual use that we improve our unikernels and the libraries that depend on them. That is why part of our tutorial also focuses on deploying unikernels, so that you can use them and help us with your feedback. Finally, we are also proposing a project called [Mollymawk][mollymawk], designed to simplify the deployment of unikernels via a web interface, so that, in future, we can offer access to our infrastructure to anyone who wishes to use it (and we would like to thank @kit-ty-kate for their various attempts). ## About us Finally, none of this would be possible without our co-operative and the way we organise our work. You can view the details of our finances [here][robur-finance]. We accept donations and have recently secured funding from NLnet. Private companies such as [Ahrefs][ahrefs] and [Tarides][tarides] also sponsor our work. We also have partnerships with [OCamlPro][ocamlpro] and the [OCaml Foundation][ocf]. And we contribute to the OCaml ecosystem on a daily basis by working with individuals and companies. You can find more details on [our website][robur.coop]. We would like to emphasise that it is thanks to our structure (that of an association) that we are able to carry out this work, and we would like to thank our sponsors. ## Unikernels and MirageOS Enthusiasts may wonder what the advantage of our approach is compared to MirageOS, a unikernel project that remains active thanks to our maintenance work. Our approach is characterised by five points that we have identified as glass ceilings to the adoption of unikernels: - the first concerns the use of [`ocaml-cstruct`][ocaml-cstruct]. The fundamental issue remains the use of `bigarray` as _buffer_ instead of `bytes`/`string`. Our experience whilst developing [MirageVPN][miragevpn] has shown us that using `cstruct` as a systematic library for the _buffer_ concept Across our entire ecosystem was our biggest **penalty** in terms of throughput for our unikernel. This is why we decided not only to develop [`bstr`][bstr] and [`slice`][bstr], but also to rewrite the majority of our libraries by replacing `cstruct` with `bytes`/`string` (this was the case for [`ocaml-tls`][ocaml-tls-pr], [`mirage-crypto`][mirage-crypto-pr], [`ocaml-dns`][ocaml-dns-pr] and, more recently, [`awa-ssh`][awa-ssh-pr] and [`utcp`][utcp-pr]). The conclusion is quite simple: it is not a question of stopping using bigarray, but rather of not making their use systematic in our aPIs. - the second point concerns the use of functors to express the devices required by the unikernel. The approach of reusing interfaces to inject specific implementations that concretize these devices via functors may seem appealing, but the maintenance cost of such an ecosystem—constrained by interfaces that are no longer really up to date with new concepts—has led us into [absurd situations][absurd] whenever we've needed to develop more specific unikernels tailored to our requirements. Once again, the rule is not to stop using functors, but not to make their use systematic. - the third point concerns the compilation of unikernels. MirageOS relies on a workaround that, even today, integrates very poorly with `dune`; this can be defined as: _multi-stage compilation_, where a code-generation phase is required depending on the desired _target_. This way of structuring the workflow with a set of dependencies dictated by the tool explicitly restricts the developer in what they wish to do and how they wish to do it. Although there are constraints on compiling a unikernel (such as not using the `Unix` module), our cooperative has decided to provide **informative** (and not prescriptive) tools that assist (but do not force) the user in designing their unikernel (furthermore, we are also delighted to introduce [`unic`][unic] and [`mfetch`][mfetch]). The key point of our new approach is to ensure that it is the user who has the final say, rather than the tool (in this case, `mirage`), in the development of unikernels. It is very intricate on what the `mirage` tool does, including generation of functor applications and some initialization stubs, as well as opam version constraints (since the functors and initialization functions may change in new opam packages). The `mirage` tool has a closed world assumption, and it is hard to use a package not known by `mirage` - it is as well very hard to use `js_of_ocaml` in a `mirage` unikernel. - the fourth point concerns the maintenance of this ecosystem, and specifically the maintenance of the tools and libraries on which MirageOS relies. Furthermore, we have experienced some disappointments with `opam-monorepo`, and we are finding, day by day, that it is becoming increasingly difficult to update `mirage-tcpip` in line with OCaml 5. Our involvement in the OCaml security team has also led us to wish to deprecate certain libraries due to a lack of maintenance. We are therefore refocusing our ecosystem on what our co-operative produces and maintains. - Finally, the last point concerns our work with the community, which remains unchanged despite the emergence of AI. We have observed a certain abusive use of AI to solve non-existent problems relating to unikernels, which, in our view, undermines our credibility regarding the security, maintenance and quality of our software. In this regard, we are trying to distance ourselves from those who prefer _vibe-coding_ rather than deployment. This approach is reflected in our use of the [AGPL][agpl] as our licence (whilst offering the option of dual licensing) and a [POLICY][policy] regarding the use of AI. All of this has led us to propose MirageOS 5, which represents a fundamental departure from the factors that characterised the failure of MirageOS 4 (for which I was the release manager). It is therefore, having acknowledged this defeat (and analysed it), that we are proposing a new approach to creating and deploying unikernels: [uniker.nl][uniker.nl]. As regards the projects associated with the MirageOS project, we will, of course, continue to maintain some of them, whilst at the same time offering more up-to-date solutions to those who use some of our libraries. We would also be delighted to help anyone wishing to migrate to our new libraries and tools; we are (as we always have been) available to assist. [unic]: https://github.com/robur-coop/unic [mfetch]: https://github.com/robur-coop/mfetch [agpl]: https://www.gnu.org/licenses/agpl-3.0.en.html [policy]: https://github.com/Solo5/solo5/blob/main/AI.md [ocaml-cstruct]: https://github.com/mirage/ocaml-cstruct [miragevpn]: https://github.com/robur-coop/miragevpn [tarides]: https://tarides.com/ [ahrefs]: https://ahrefs.com/ [robur-finance]: https://blog.robur.coop/articles/finances.html [mollymawk]: https://mollymawk.robur.coop/ [bstr]: https://github.com/robur-coop/bstr [immuable]: https://github.com/dinosaure/immuable [uniker.nl]: https://uniker.nl/ [builds.robur.coop]: https://builds.robur.coop [zostera]: https://github.com/robur-coop/zostera [workshop]: https://ocaml.org/ocaml-workshop-2026 [slides]: https://docs.google.com/presentation/d/1bOXJAPEEjPBx_8i0frThKhLpHd8C7-EPRczEQjvz6JU/edit?slide=id.g3f7268904c5_0_1#slide=id.g3f7268904c5_0_1 [uniker.nl-tutorial]: https://uniker.nl/introduction/ [yocaml]: https://yocaml.github.io/tutorial/index.html [blog-robur]: https://blog.robur.coop/ [miou-announce]: https://discuss.ocaml.org/t/ann-miou-a-simple-scheduler-for-ocaml-5/12963 [sans-io]: https://discuss.ocaml.org/t/on-concurrency-models/15899/13 [sans-io-2]: https://discuss.ocaml.org/t/why-ocsigen-is-staying-on-lwt-for-now/18318/27 [digestif]: https://github.com/mirage/digestif [mnet]: https://discuss.ocaml.org/t/ann-mnet-a-new-tcp-ip-stack-for-unikernels-in-ocaml/17851 [mirage-crypto]: https://github.com/mirage/mirage-crypto [solo5]: https://github.com/solo5/solo5 [mkernel]: https://github.com/robur-coop/mkernel [lwt]: https://github.com/ocsigen/lwt [tlstunnel]: https://github.com/robur-coop/tlstunnel [contruno]: https://github.com/robur-coop/contruno [mailingl.st]: https://mailingl.st [ptt]: https://github.com/robur-coop/ptt [albatross]: https://github.com/robur-coop/albatross [aussi]: https://github.com/robur-coop/aussi [chaos]: https://github.com/robur-coop/chaos [unipi]: https://github.com/robur-coop/unipi [mnet]: https://github.com/robur-coop/mnet [ocaml-solo5]: https://github.com/mirage/ocaml-solo5 [utcp]: https://github.com/robur-coop/utcp [codept]: https://github.com/Octachron/codept [mnet-announce]: https://discuss.ocaml.org/t/ann-mnet-a-new-tcp-ip-stack-for-unikernels-in-ocaml/17851 [mhttp]: https://github.com/robur-coop/mhttp [mfat]: https://github.com/robur-coop/mfat [vifu]: https://github.com/robur-coop/vif [msendmail]: https://github.com/robur-coop/colombe [dns-primary-git]: https://github.com/robur-coop/dns-primary-git/ [mnet-secondary]: https://git.robur.coop/robur/dns-secondary/ [uniker.nl-repo]: https://github.com/robur-coop/uniker.nl [orb]: https://github.com/robur-coop/orb [grafana]: https://grafana.com/ [influxdb]: https://www.influxdata.com/ [telegraf]: https://www.influxdata.com/time-series-platform/telegraf/ [ocamlpro]: https://ocamlpro.com [ocf]: https://ocaml-sf.org/ [robur.coop]: https://robur.coop/ [ocaml-tls-pr]: https://github.com/mirleft/ocaml-tls/pull/497 [mirage-crypto-pr]: https://github.com/mirage/mirage-crypto/pull/214 [ocaml-dns-pr]: https://github.com/mirage/ocaml-dns/pull/351 [awa-ssh-pr]: https://github.com/mirage/awa-ssh/pull/85 [utcp-pr]: https://github.com/robur-coop/utcp/pull/55 [absurd]: https://github.com/robur-coop/dnsvizor/blob/83b32596c4a08c080067e1cd36fe5788b90cdd78/unikernel.ml#L610-L631 [caldav]: https://github.com/robur-coop/caldav -- Romain Calascibetta - http://din.osau.re/