Skip to content
Switch branches/tags

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time

making a new git repository

First, cd into a new directory and initialize the repository:

git init

The Mirage toolchain generates a lot of files automatically as part of the build process. Since those are site-specific, you do not need or want to share them with others. We can make git ignore them by adding them to the .gitignore file:

cat >.gitignore <<EOF


The mirage-skeleton repository contained an intimidating amount of repositories. This repository is an attempt to simplify the process of getting started with Mirage.

The parts we will need to get our first unikernel running are:

  • mirage: the command-line tool used to generate the require build code and configuration files (think of it like make)
    • mirage configure: takes switches like --unix or --xen to specify the target of the build.
    • mirage clean: deletes generated files (like make clean)
  • the mirage tool will look for this specially named file in the current directory.

To find the definitions of Mirage types, look into the ~/.opam/*/lib/mirage/ directory.

The Mirage ecosystem makes use of the functoria domain-specific language for piecing things together, so if you encounter alien syntax, the documentation for that might serve as a helpful utility. For example, the documentation for the @-> syntax.

Job module structure

The compiled unikernel can run a set of "jobs" concurrently using the Lwt lightweight thread module (a library for cooperative concurrency, like Python's "green threads").

Mirage uses OCaml's type system to be able to accomodate parametric compilation (that is, support different backends). Specifically, you need to be familiar with OCaml's "modules" and the concept of "functors".

Each of the unikernel jobs may depend on various Mirage components like the console to provide console input/output (Mirage_console), or the read-only key-value store (elegantly named V1_LWT.KV_RO TODO this has been changed in v3).

To make use of these components in your job, your module needs to be "parameterized over them" - that is, you need to implement the job module as a functor to use these components.

Additionally, each job needs to implement a start function which returns an Lwt handle (for more information, find a tutorial on concurrency in OCaml using Lwt TODO). Implementing a no-op job

Example of the type definition of a job module for a Mirage:

module type Job_t =
  val start : unit Lwt.t

A very basic implementation of a no-op job (

module Job =
  val start = Lwt.return_unit

A for the no-op job:

open Mirage

let my_noop_job =
  foreign "Noop.Job" (Mirage.job)
  (* *)

let () =
  (* Mirage.register takes a string (the name of your unikernel) and a list of jobs to run concurrently *)
  Mirage.register "mything"
  [ my_noop_job ]

Compiling the no-op unikernel:

mirage configure && make

Running the no-op unikernel:

./mir-mything Providing output: Hello, World!

Now that we can compile a unikernel, we would like to extend it to print "Hello, World!" to the console. In order to do that we need to make a job that uses the Mirage "console" component.

We make another module to contain this job (

module type Job_t =
(* note that providing a signature / module type like this is entirely optional *)
functor (Console : Mirage_console.S) ->
  val start : Console.t -> unit Lwt.t

module Job : Job_t =
functor (Console : Mirage_console.S) ->
  let start (my_console : Console.t) =
    Lwt.return (Console.log my_console "Hello, World!")

And we need to change to include our job:

open Mirage

let my_noop_job =
  foreign "Noop.Job" (Mirage.job)
  (* *)

let hello_world =
  foreign "Hello_world.Job" (Mirage.console @-> Mirage.job)
  (* "@->" is Functoria syntax: *)
  (* Mirage.console: *)

let () =
  (* Mirage.register takes a string (the name of your unikernel) and a list of jobs to run concurrently *)
  Mirage.register "mything"
  [ my_noop_job
  ; hello_world $ Mirage.default_console
  (* dollar sign:$%29 *)

Example run:

root@localhost:~/ocaml/mirage-examples# mirage configure && make
ocamlbuild -use-ocamlfind -pkgs functoria.runtime,mirage-console.unix,mirage-types.lwt,mirage-unix,mirage.runtime -tags "warn(A-4-41-44),debug,bin_annot,strict_sequence,principal,safe_string" -tag-line "<static*.*>: warn(-32-34)" -cflag -g -lflags -g,-linkpkg main.native
Finished, 13 targets (0 cached) in 00:00:00.
ln -nfs _build/main.native mir-mything
root@localhost:~/ocaml/mirage-examples# ./mir-mything 
Hello, World!
root@localhost:~/ocaml/mirage-examples# Customizing the unikernel with command-line options

Mirage can take command-line options using "keys" (see the Mirage_key module).

The keys can be specified both at build-time and (when compiling for the --unix target) at run-time, using mirage configure --my-key at build-time, or invoking it with ./mir-mything --my-key at run-time.

The keys are registered with Mirage using Key.create in and are accessible as a function Key_gen.<name> : unit -> string from the job modules (a file called containing the code to enable this is generated by mirage configure).

The hello_xyz job looks like this:

module type Job_t =
functor (Console : Mirage_console.S) ->
  val start : Console.t -> unit Lwt.t

module Job : Job_t =
functor (Console : Mirage_console.S) ->
  let start (my_console : Console.t) =
    Lwt.return @@
    Console.log my_console
      ("Hello, " ^ Key_gen.(my_name () ) ^ "!" )

We need to make some modifications to the, and mirage configure used to allows us to have multiple config files in the same directory by using the -f switch, but now someone decided it would be a great idea to remove that, so to avoid cluttering the old examples, we create a new directory hello_xyz and a hello_xyz/

open Mirage

let my_hello_xyz =
  let key =
    let doc =
      ~doc:"Specify a name for the hello_world_xyz job"
    Mirage.Key.(create "my_name" Arg.(opt string "John Doe" doc) )
    ~keys:[Mirage.Key.abstract key]
      (* *)
    (Mirage.console @-> Mirage.job)

let () =
    [ my_hello_xyz $ Mirage.default_console

Finally we can compile:

cd hello_xyz/
mirage configure

Running it looks like:

root@localhost:~/ocaml/mirage-examples/hello_xyz# ./mir-hello_xyz
Hello, John Doe!
root@localhost:~/ocaml/mirage-example/hello_xyzs# ./mir-hello_xyz --name Jane
Hello, Jane!

(* TODO nice example code at *)

(* TODO section on implement an argument converter: *)

(* TODO Unfortunately I have to venture AFK now, but I hope to continue this log of my adventures with Mirage. *)


Work-in-Progress collection of examples of using MirageOS from OCaml




No releases published


No packages published